🔌 Lesson 5.5: Interfaces (UINTERFACE)
An interface is a contract: "any type that implements me can do X." A door, a chest, an NPC, and a switch are wildly different classes — but all can be "interacted with." Interfaces let your code ask "can you do this?" without caring what something is. This closes the coupling spectrum from Module 3.5.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Explain what an interface solves that casting and components don't
- Declare an Unreal interface (the
UINTERFACE+I-class pair) - Implement it on unrelated actor classes
- Call interface functions correctly with the
Execute_convention - Check for and call the interface safely, including from Blueprint implementers
Estimated Time: 60 minutes
Engine Version: Unreal Engine 5.8
In This Lesson
The Problem Interfaces Solve
Recall the anti-pattern from Lesson 3.5: a chain of casts to handle many types that all share a behavior.
// ❌ The chain-of-casts anti-pattern:
if (ADoor* Door = Cast<ADoor>(Target)) { Door->Open(); }
else if (AChest* Chest = Cast<AChest>(Target)) { Chest->Open(); }
else if (ANPC* Npc = Cast<ANPC>(Target)) { Npc->Talk(); }
// ...every new interactable type means editing THIS function.
An interface replaces all of it with one question: "Target, are you interactable? If so, interact." The caller never names a concrete type, and adding a new interactable never touches this code.
📖 Interface vs component — both express capability
Both FindComponentByClass (Lesson 3.5) and interfaces answer "can you do X?" Use a component when the capability comes with state and behavior you want to share (health, inventory). Use an interface when types implement the same action their own way (a door opens differently than a chest) with no shared state to bundle. They're complementary tools.
Declaring an Interface
Unreal interfaces are a pair: a UINTERFACE class (reflection boilerplate you never touch) and an I-prefixed class (where your functions live). Create via New C++ Class → Unreal Interface.
// Interactable.h
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "Interactable.generated.h"
// 1) The UINTERFACE — pure boilerplate; do not add members here.
UINTERFACE(MinimalAPI, Blueprintable)
class UInteractable : public UInterface
{
GENERATED_BODY()
};
// 2) The I-class — your actual contract. Implementers inherit from THIS.
class IInteractable
{
GENERATED_BODY()
public:
// BlueprintNativeEvent: has a C++ default AND can be overridden in Blueprint.
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
void Interact(AActor* Interactor);
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interaction")
FText GetInteractionPrompt() const;
};
⚠️ Two classes, one purpose — don't mix them up
The U-class (UInteractable) exists only so the reflection system knows the interface; you never add functions to it or inherit from it directly. Your classes implement the I-class (IInteractable), and that's where the functions are declared. Blueprintable on the UINTERFACE lets Blueprints implement it too.
Implementing It
Any class implements the interface by inheriting the I-class and overriding its functions. Because these are BlueprintNativeEvents, the C++ override is named with an _Implementation suffix.
// Door.h — a door that is interactable.
UCLASS()
class MYPROJECT_API ADoor : public AActor, public IInteractable
{
GENERATED_BODY()
public:
// Note the _Implementation suffix for BlueprintNativeEvent functions:
virtual void Interact_Implementation(AActor* Interactor) override;
virtual FText GetInteractionPrompt_Implementation() const override;
};
// Door.cpp
void ADoor::Interact_Implementation(AActor* Interactor)
{
bIsOpen = !bIsOpen;
// ...play the open/close animation, toggle collision...
UE_LOG(LogTemp, Log, TEXT("Door %s"), bIsOpen ? TEXT("opened") : TEXT("closed"));
}
FText ADoor::GetInteractionPrompt_Implementation() const
{
return bIsOpen ? FText::FromString(TEXT("Close"))
: FText::FromString(TEXT("Open"));
}
A completely unrelated class — say AChest — implements the same interface with its own behavior. That's the whole point: shared contract, independent implementations, no common base class forced on them.
(contract: Interact, GetPrompt)"] --> D["ADoor : AActor
opens/closes"] I --> C["AChest : AActor
opens, gives loot"] I --> N["ANPC : ACharacter
starts dialogue"]
Calling with Execute_
Here's the Unreal-specific twist. Because an implementer might be a Blueprint, you don't call interface functions directly — you call them through a generated static Execute_ wrapper that routes to the right implementation (C++ or Blueprint).
#include "Interactable.h"
void AMyCharacter::TryInteract()
{
AActor* Target = GetLookedAtActor(300.0f); // from Lesson 5.2's trace
if (!IsValid(Target)) { return; }
// Does this actor implement the interface?
if (Target->Implements<UInteractable>()) // note: the U-class here
{
// Call through Execute_ — works whether implemented in C++ or Blueprint.
IInteractable::Execute_Interact(Target, this);
}
}
⚠️ The three gotchas of interface calls
- Check with the
U-class:Target->Implements<UInteractable>()usesU, notI. - Call with
Execute_:IInteractable::Execute_Interact(Object, Args...)— never callInteract()directly on aBlueprintNativeEvent, or you'll skip Blueprint implementations. - First arg is the object: the
Execute_wrapper takes the implementing object as its first parameter, then the function's own arguments.
✅ Why the ceremony is worth it
The Execute_ convention is what lets a designer make a Blueprint actor implement IInteractable and have your C++ interactor call it seamlessly. The C++ defines the contract; Blueprint can fulfill it. That's the hybrid model (Lesson 1.1) at the interface level — and precisely the kind of C++/Blueprint boundary Module 6 is all about.
The Interaction System
Assembling the module: the character traces for what it's looking at (5.2), asks if it's interactable (5.5), and interacts. A prompt UI can show GetInteractionPrompt. Adding a new interactable type — a lever, a terminal, a pet — requires zero changes to the character.
void AMyCharacter::UpdateInteractionPrompt()
{
AActor* Target = GetLookedAtActor(300.0f);
if (IsValid(Target) && Target->Implements<UInteractable>())
{
const FText Prompt = IInteractable::Execute_GetInteractionPrompt(Target);
// ...show Prompt on the HUD: "Press E to " + Prompt...
}
else
{
// ...hide the prompt...
}
}
💡 The spectrum, complete: Module 3.5 promised interfaces and delegates as the loose end of the coupling spectrum. You've now built both — delegates for "X happened, whoever cares" (5.4) and interfaces for "can you do X?" (5.5). Between direct references, components, interfaces, and events, you have the full communication toolkit. Choosing among them well is a hallmark of clean Unreal C++.
Hands-on Exercise & Quiz
🏋️ Exercise: An IDamageable interface
Objective: Contrast interface vs component for the same capability.
- Declare
IDamageablewith aBlueprintNativeEventvoid TakeDamage(float Amount, AActor* Causer). - Implement it on an
ABarrelthat explodes at zero health. - From a projectile, check
Implements<UDamageable>()and callExecute_TakeDamage. - Discuss: when would you prefer the
UHealthComponentapproach (3.3/3.5) over this interface?
✅ Discussion
Use the component when damageable things share real state and behavior (current/max health, death event, regen) — you get all that for free and consistently. Use the interface when "take damage" means something totally different per type and there's no shared state worth bundling (a barrel that just explodes, a shield that deflects). Many real projects combine them: an IDamageable interface whose implementers often forward to a UHealthComponent.
🎯 Quick Quiz
Question 1: Which class do implementers inherit from?
Question 2: How do you call a BlueprintNativeEvent interface function?
Question 3: When is an interface a better fit than a shared component?
Summary
🎉 Key Takeaways
- An interface is a "can you do X?" contract shared across unrelated types — it replaces chains of casts.
- Unreal interfaces are a pair: the
UINTERFACEboilerplate and theI-class where functions live; implementers inherit theI-class. BlueprintNativeEventfunctions are overridden in C++ with an_Implementationsuffix and can also be implemented in Blueprint.- Check with
Implements<UInterface>()and call viaIInterface::Execute_Fn(Object, Args)— never call the function directly. - Use interfaces for "same action, own implementation, no shared state"; use components when there's shared state/behavior — they complement each other.
📚 Additional Resources
🚀 What's Next?
Module 5 is complete — collision, traces, timers, delegates, and interfaces give you the full interaction toolkit. Module 6 is the heart of the course: C++ ↔ Blueprint interop — everything you've been hinting at, made explicit, so your C++ and designers' Blueprints become one seamless workflow.
🎉 Module 5 complete!
The interaction toolkit is yours. Now let's marry C++ and Blueprint for real.