C++ Interface w/ default implementation that's usable in C++ and BP

Hi all,

I have a custom damage interface that I like to use. Problem is, I currently can’t use it in Blueprints with default implementation it seems. Is there a way to get around this somehow? What would be the best thing I could do to get it so I can have interfaces with default impelementations that are usable in C++ and blueprint? Right now, it won’t even let me implement in blueprint (due to CannotImplementInterfaceInBlueprint, but I get other errors if I remove this meta tag).

DamageableInterface.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "DamageableInterface.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI, meta = (CannotImplementInterfaceInBlueprint))
class UDamageableInterface : public UInterface
{
	GENERATED_BODY()
};

/**
 * 
 */
class HALOFLOODFANGAME01_API IDamageableInterface
{
	GENERATED_BODY()

public:
	UFUNCTION()
	virtual float CustomOnTakeAnyDamage(float DamageAmount, FVector Force, AController* EventInstigator = nullptr, AActor* DamageCauser = nullptr);
	
	UFUNCTION(BlueprintCallable)
	virtual float CustomTakeDamage(float DamageAmount, FVector Force, FDamageEvent const& DamageEvent, AController* EventInstigator = nullptr, AActor* DamageCauser = nullptr);

	UFUNCTION(BlueprintCallable)
	virtual float CustomTakePointDamage(FPointDamageEvent const& PointDamageEvent, float Force, AController* EventInstigator = nullptr, AActor* DamageCauser = nullptr);

	UFUNCTION(BlueprintCallable)
	virtual float CustomTakeRadialDamage(float Force, FRadialDamageEvent const& RadialDamageEvent, AController* EventInstigator = nullptr, AActor* DamageCauser = nullptr);

	UFUNCTION(BlueprintCallable)
	virtual UHealthComponent* GetHealthComponent();

	UFUNCTION(BlueprintCallable)
	virtual float ChangeHealth(float Damage, FVector Force = FVector(0,0,0), FVector HitLocation = FVector(0,0,0), FName HitBoneName = "", AController* EventInstigator = nullptr, AActor* DamageCauser = nullptr, bool bIgnoreShields = false, bool bIgnoreHealthArmor = false, bool bIgnoreShieldArmor = false);
	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
};

DamageableInterface.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "DamageableInterface.h"

#include "HealthComponent.h"
#include "Core/CharacterBase.h"
#include "Engine/DamageEvents.h"
#include "Kismet/GameplayStatics.h"

// Add default functionality here for any IDamageableInterface functions that are not pure virtual.

float IDamageableInterface::CustomOnTakeAnyDamage(float DamageAmount, FVector Force,
	AController* EventInstigator, AActor* DamageCauser)
{
	return DamageAmount;
}

float IDamageableInterface::CustomTakeDamage(float DamageAmount, FVector Force, FDamageEvent const& DamageEvent,
                                             AController* EventInstigator, AActor* DamageCauser)
{
	return ChangeHealth(DamageAmount, Force, FVector(0,0,0), FName(""), EventInstigator, DamageCauser);
}

float IDamageableInterface::CustomTakePointDamage(FPointDamageEvent const& PointDamageEvent, float Force,
                                            AController* EventInstigator, AActor* DamageCauser)
{
	return ChangeHealth(PointDamageEvent.Damage, PointDamageEvent.ShotDirection * Force, PointDamageEvent.HitInfo.Location, PointDamageEvent.HitInfo.BoneName, EventInstigator, DamageCauser);
}

float IDamageableInterface::CustomTakeRadialDamage(float Force,
	FRadialDamageEvent const& RadialDamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	return ChangeHealth(RadialDamageEvent.Params.BaseDamage, (Cast<AActor>(this)->GetActorLocation() - RadialDamageEvent.Origin).GetSafeNormal() * Force, FVector(0,0,0), FName(""), EventInstigator, DamageCauser);
}

UHealthComponent* IDamageableInterface::GetHealthComponent()
{
	return Cast<AActor>(this)->FindComponentByClass<UHealthComponent>();
}

float IDamageableInterface::ChangeHealth(float Damage, FVector Force, FVector HitLocation, FName HitBoneName,
	AController* EventInstigator, AActor* DamageCauser, bool bIgnoreShields, bool bIgnoreHealthArmor,
	bool bIgnoreShieldArmor)
{
	CustomOnTakeAnyDamage(Damage, Force, EventInstigator, DamageCauser);
	if (GetHealthComponent())
		return GetHealthComponent()->TakeDamage(Damage, Force, HitLocation, HitBoneName, EventInstigator, DamageCauser, false, false, false);
	return Damage;
}

1 Like

You need a virtual function with _Implementation to be able to override it

UFUNCTION(BlueprintNativeEvent,BlueprintCallable)
float CustomOnTakeAnyDamage(float DamageAmount, FVector Force, AController* EventInstigator = nullptr, AActor* DamageCauser = nullptr);|

virtual float CustomOnTakeAnyDamage_Implementation(float DamageAmount, FVector Force, AController* EventInstigator = nullptr, AActor* DamageCauser = nullptr);|

and in the cpp you only need to define the implementation

float IDamageableInterface::CustomOnTakeAnyDamage_Implementation(float DamageAmount, FVector Force,
	AController* EventInstigator, AActor* DamageCauser)
{
	return DamageAmount;
}

Then once a class implements the interface you can override it in BP and call the parent version if you want the c++ to execute.

over1

Though the nested use of interface functions in your case opens up a whole can of worms that might be tough to get through.

and in your class you can also redefine the internals of the class via

float CustomOnTakeAnyDamage_Implementation(float DamageAmount, FVector Force,
	AController* EventInstigator, AActor* DamageCauser) override; 

(you can make it virtual if you want inheritance to be able to further override it)

Few additional notes :

  • Make sure the calling code uses Object->Implements<UDamageableInterface> to check for interface, and not Cast, otherwise blueprint implementers will not be detected.

  • Make sure the calling code uses the IDamageableInterface::Execute_Function(InObject, ...) pattern, otherwise BP overrides will not be called. You’ll probably get an error if you don’t use that anyways. But don’t fall into the trap of calling directly the *_Implementation version.

  • This includes calls from within the interface. For example when you call CustomOnTakeAnyDamage from ChangeHealth, this will not call the blueprint override if there is one. Same for GetHealthComponent(), although this one might work regardless because it relies on FindComponentByClass, but still… You should use Execute_* pattern everywhere. Execute_* requires an UObject and will not accept this, but either Cast<UObject*>(this) or _getUObject() (faster) should work.

Just to add some info on the Execute_ commands. They need compiled files (.generated) for IDE’s to recognize the commands. Otherwise they will be flagged as errors.