Skip to main content

🔌 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.

graph TD I["IInteractable
(contract: Interact, GetPrompt)"] --> D["ADoor : AActor
opens/closes"] I --> C["AChest : AActor
opens, gives loot"] I --> N["ANPC : ACharacter
starts dialogue"]
Figure 1: One interface, three unrelated implementers with different base classes. The interactor code treats them identically.

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>() uses U, not I.
  • Call with Execute_: IInteractable::Execute_Interact(Object, Args...) — never call Interact() directly on a BlueprintNativeEvent, 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.

  1. Declare IDamageable with a BlueprintNativeEvent void TakeDamage(float Amount, AActor* Causer).
  2. Implement it on an ABarrel that explodes at zero health.
  3. From a projectile, check Implements<UDamageable>() and call Execute_TakeDamage.
  4. Discuss: when would you prefer the UHealthComponent approach (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 UINTERFACE boilerplate and the I-class where functions live; implementers inherit the I-class.
  • BlueprintNativeEvent functions are overridden in C++ with an _Implementation suffix and can also be implemented in Blueprint.
  • Check with Implements<UInterface>() and call via IInterface::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.