Quick answer: Use IMyInterface::Execute_FunctionName(Target) — not direct virtual calls. Define the C++ side with UFUNCTION(BlueprintNativeEvent) and override the _Implementation suffix.
You author a UInterface called IInteractable with an Interact function. You implement it in C++ on a parent class and in Blueprint on a child. When C++ code calls InteractableActor->Interact(), the linker complains. When you call it via Cast<IInteractable>, the Blueprint child is silently skipped. Both approaches feel correct but neither works for the full interface contract.
How UInterfaces Differ from Plain C++ Virtuals
UInterfaces are a Blueprint-friendly variant of multiple inheritance. They’re not pure C++ interfaces — they participate in the UObject reflection system, can be implemented in Blueprints, and must dispatch through a generated thunk to support both languages.
Each interface function with BlueprintNativeEvent or BlueprintImplementableEvent gets three generated forms:
FunctionName(...)— the C++ declaration; never call directly.FunctionName_Implementation(...)— the C++ entry point; override this.Execute_FunctionName(UObject* Target, ...)— the static dispatcher; always call this.
The dispatcher checks whether Target implements the interface. If yes, it routes to either the Blueprint graph (if overridden) or the C++ _Implementation.
Step 1: Declare the Interface Correctly
// IInteractable.h
UINTERFACE(MinimalAPI, Blueprintable)
class UInteractable : public UInterface { GENERATED_BODY() };
class MYGAME_API IInteractable
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Interaction")
void Interact(AActor* Instigator);
};
Step 2: Implement on a C++ Class
// MyDoor.h
UCLASS()
class AMyDoor : public AActor, public IInteractable
{
GENERATED_BODY()
public:
virtual void Interact_Implementation(AActor* Instigator) override;
};
// MyDoor.cpp
void AMyDoor::Interact_Implementation(AActor* Instigator) {
UE_LOG(LogTemp, Log, TEXT("Door opened by %s"), *Instigator->GetName());
}
Note the _Implementation suffix on both declaration and definition. Without it, the engine won’t find your implementation and Blueprint child overrides won’t inherit it correctly.
Step 3: Call via Execute_
// caller
if (Target && Target->Implements<UInteractable>())
{
IInteractable::Execute_Interact(Target, this);
}
Execute_Interact handles both:
- If Target is a Blueprint that overrode the interface, the Blueprint graph runs.
- If Target is a C++ class implementing the interface,
Interact_Implementationruns.
Don’t call Target->Interact(this) directly. The dispatch is statically resolved and won’t find Blueprint overrides.
Common Mistake: Cast<IInteractable>
You might be tempted to:
if (IInteractable* I = Cast<IInteractable>(Target))
I->Interact_Implementation(this); // finds only C++ implementations
This works for C++ implementations but misses Blueprint-only ones — Cast<I> only matches if the class’s native UClass inherits the interface. A Blueprint that adds the interface in its class settings doesn’t satisfy this. Always check Implements<U> and dispatch through Execute_.
BlueprintImplementableEvent vs BlueprintNativeEvent
- BlueprintImplementableEvent — no C++ implementation allowed; the Blueprint must provide one.
Execute_FunctionNamereturns a default value if not overridden. - BlueprintNativeEvent — C++ provides a default in
_Implementation; Blueprint may override.
Use BlueprintNativeEvent when there’s a sensible default; use BlueprintImplementableEvent for pure callbacks that only make sense per-derived-Blueprint.
Verifying
Build a test: a Blueprint actor that adds BPI_Interactable in its class settings and overrides Interact to print a string. From C++, call Execute_Interact on that actor. The Blueprint string should print. Then a C++ subclass with its own Interact_Implementation — that one should print its C++ log.
“Direct calls = native only. Execute_ = both languages. Always go through the static dispatcher.”
Put a checkf(Target->Implements<U>(), TEXT(…)) guard in dev builds — saves hours of “why isn’t this firing” debugging.