UE4 C++ Interfaces - Hints n Tips

· by Steve · Read in about 10 min · (1943 Words)


I wrote this blog post to fill a few gaps in the documentation I’d found for C++ interfaces in Unreal. Specifically I have a few tips for what I think are common usage scenarios that no blog post I found talked about.

Note: This blog post is based on Unreal Engine version 4.25.3

What are Interfaces?

Interfaces are great in any language. They let you define a set of functionality that anything can implement, and not be tied to a rigid class hierarchy. My favourite implementation of this is actually in Go, where you don’t even have to declare that you’re implementing an interface - if you happen to have the right combination of functions on your type, you just are. Lovely.

C++ technically doesn’t have interfaces, but it does have multiple inheritance, which can be made to emulate it. This is what UE4 does, with a bunch of boilerplate tacked on to make it a bit more explicit, and to support the reflection system.

Why another blog post about this?

There are a bunch of blog posts out there teaching you how to use UE4 C++ interfaces, and I used them to get up to speed (I’m not an expert, I only started using them recently).

But I found pretty quickly that they skip over a few things that I needed almost immediately. For example they generally assume you’re working entirely within C++, when I think a lot of us are working in mixed C++ / Blueprint environments. They also didn’t talk about what you need to do when you want interface variables. So I felt I could add something from what I’ve learned in the last few weeks.

I could just refer to the other tutorials for the basics, but I decided to make this post self-contained and cover C++ interfaces from the ground up as well.

So this post will cover:

  • Creating and implementing a C++ interface
  • Implementing this interface directly in Blueprints as well as C++
  • Storing variables typed to the interface in C++ and Blueprints

Declaring the C++ Interface

I like to declare my interfaces in C++ and not as Blueprint Interfaces, because it gives me more options; C++ interfaces can be used in both C++ and Blueprints, while Blueprint Interfaces can only be used in Blueprints.

Let’s say we’re creating an interface called DoSomeThings which, well, does some things. First we need a header file:

#pragma once
#include "CoreMinimal.h"
#include "DoSomeThings.generated.h"

class UDoSomeThings : public UInterface
    // This will always be empty!

    // ... Here is where we will define UFUNCTION interface methods

As you can see, you have a UDoSomeThings and IDoSomeThings - there doesn’t seem to be any link between them, but the naming convention actually does this automatically thanks to UE header magic.

Adding methods

So next, you’ll want to add some methods to the interface, which you always do inside the IDoSomeThings class. Lets start with something simple:

    /// Get the number of things
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="Things")
    int GetNumberOfThings();

There’s a couple of things to note there:

  1. I’ve made it BlueprintCallable, because I want to be able to use it in Blueprints
  2. I’ve said BlueprintNativeEvent because I want to give C++ classes the option of implementing it

Why BlueprintNativeEvent?

Usually when you declare BlueprintNativeEvent it means you immediately want to add a C++ implementation, named in this case GetNumberOfThings_Implementation. But, since this is just the interface definition, you don’t have to do that yet. You wait until you actually implement this interface on a C++ class to do that.

However: I’ve discovered that this doesn’t mean you must have a C++ implementation. In my testing you can totally implement this interface directly in Blueprints with no C++ intermediate class if you want, the BlueprintNativeEvent just allows you to have C++ implementors as well.

As such I don’t see why you’d use any other specifier; the documentation says the other options are: no specifier (can’t be implemented in BP), or BlueprintImplementableEvent (must be implemented in BP, can’t be implemented in C++). Since BNE seems to let you do anything I can’t see why the others are useful, in interfaces specifically. 🤷‍♂️

Implementing the Interface in C++

Here’s a minimal C++ implementation of the interface on an Actor called SomeThingsActor:

#pragma once

#include "CoreMinimal.h"
#include "DoSomeThings.h"
#include "SomeThingsActor.generated.h"

class YOURPROJECT_API ASomeThingsActor : public AActor, public IDoSomeThings
    virtual int GetNumberOfThings_Implementation() override;
#include "SomeThingsActor.h"

int ASomeThingsActor::GetNumberOfThings_Implementation()
    return 1;

Easy, right? The only interesting thing is that because the interface definition was set to BlueprintNativeEvent, we implement GetNumberOfThings_Implementation rather than GetNumberOfThings.

Implementing the Interface in Blueprints

As I mentioned earlier, BlueprintNativeEvent doesn’t stop you implementing this interface directly in Blueprints if you want - at least in my experience. I don’t know if this is by accident or by design. It’s specific to interfaces, if you use BlueprintNativeEvent on a regular UCLASS you must have a C++ implementation, but it’s in the same class in that case.

You can also subclass a Blueprint class from a C++ implementation like ASomeThingsActor above, and override the method. But you don’t have to, with interfaces.

Here’s how you’d implement it directly. On your Blueprint class:

  • Click on “Class Settings” in the toolbar
  • In the “Details” pane under Interfaces, click “Add” next to “Implemented Interfaces”.
  • Pick the interface:

    Implementing in BP

  • Under “My Blueprint” you’ll see the interface definition:

    Implementing in BP

  • Double-click it and implement however you like:

    Implementing in BP

Detecting & Calling the Interface

Calling in C++

A lot of information online will tell you that you can just use Cast<> to both detect and access an interface in C++:

auto I = Cast<IDoSomeThings>(Actor);
if (I)
    int Num = I->GetNumberOfThings();

It’s true that this works if Actor is a C++ implementation of IDoSomeThings, or a Blueprint subclass of a C++ implementor.

If, however, you implement this interface directly on a Blueprint, Cast<IDoSomeThings> will always return nullptr. Always. This is because the C++ side has no idea the actor implements this. This is particularly nefarious because nothing will look wrong until you happen to use a trivial direct BP implementation and suddenly a bunch of things will start to fail silently.

To detect interfaces reliably, whether they’ve been implemented as C++ or Blueprints, you need avoid using Cast<> and use UE4’s explicit interface utilities which use the full reflection system and can therefore detect Blueprints.

First, detecting if the interface is implemented:

/// Either of these 2 are valid

/// Option 1 does more sanity checks, is null-safe on Actor
bImpl = UKismetSystemLibrary::DoesImplementInterface(Actor, UDoSomeThings::StaticClass())

/// Option 2 skips some checks, you must ensure Actor is non-null
bImpl = Actor->GetClass()->ImplementsInterface(UDoSomeThings::StaticClass())

Once you’ve detected if the interface is present, you must call it via the interface wrappers, not directly on the instance:

if (bImpl)
    int Num = IDoSomeThings::Execute_GetNumberOfThings(Actor);

This will work universally, whether the interface has been implemented in C++ or Blueprints, so it seems best to always use this method. It’s probably less efficient, but personally I tend to use interfaces at a fairly high level so am not particularly concerned.

Calling in Blueprints

This is a little simpler because there’s really only one way to do it; you can call any interface on any object:

Calling in BP

Notice the envelope icon which shows that this is a “Message” to the receiving object, which is silently ignored if that interface is not implemented. It works whether the interface was implemented in C++ or Blueprints. When you pick the function from the list, it displays like this:

Calling in BP

If you specifically need to know whether an object implements an interface, you can test with the “Does Implement Interface” node:

Calling in BP

Storing Interfaces As Variables

Sometimes you want to hold on to a reference to an interface, as the general type.

Blueprint Variables

In Blueprints this is pretty easy. You need to make sure your “U” interface class has the BlueprintType specifier, which allows it to be stored in Blueprint variables:

UINTERFACE(MinimalAPI, BlueprintType)
class UDoSomeThings : public UInterface

Once you’ve done that, you can just create variables with this type inside your Blueprints like any other variable:

BP Variable

C++ variables

I usually prefer to define my variables in C++, because then I can use them in C++ code or Blueprints.

In C++ things are slightly more tricky, because you can’t store a IDoSomeThings pointer as a UPROPERTY(), because they don’t play nice with the garbage collector. We need something that holds on to the UObject.

TScriptInterface (Prone to error!)

Spoiler: You might wonder in a minute why I talk about TScriptInterface only to tell you in the end not to use it. Well, it’s because it’s actually suggested by the compiler if you try to use an interface pointer as a UPROPERTY. That’s how I found it, only to discover its problems later.

Unreal provides a TScriptInterface<> type which is designed to wrap an object as a specific interface. It’s the official suggested way to hold on to both the object reference safely and the interface it’s supposed to be presenting. You can use it like this:

TScriptInterface<IDoSomeThings> SomethingInstance;

TScriptInterface<> is used as a value type, so rather than null when not initialised, the default state is an empty wrapper. To test whether it’s empty or not, use the bool() overloaded operator:

if (SomethingInstance)
    // OK this points to something useful

The simplest way to assign an object which implements the interface to this property is to use the assignment operator:

if (UKismetSystemLibrary::DoesImplementInterface(Actor, UDoSomeThings::StaticClass()))
    SomethingInstance = Actor;

When you want to use this stored reference, you call it using the overloaded -> operator, i.e.:

int Num = SomethingInstance->GetNumberOfThings();

Simple in theory, but there’s one major problem with this, and that’s that TScriptInterface<> derives the interface pointer using Cast<>. And as we established earlier, Cast<> will always return nullptr if the instance in question is only implementing the interface at the Blueprint level, and none of its superclasses implement it in C++.

The thing is, you can still make TScriptInterface<> work in this scenario, by instead calling:

int Num = IDoSomeThings::Execute_GetNumberOfThings(SomethingInstance.GetObject());

This is because the UObject pointer in the TScriptInterface is fine, but the interface pointer is null.

Also, calls in Blueprints will work fine with this TScriptInterface<> variable, which can hide the problem. Various methods of TScriptInterface<> will fail in C++ if your class only implements at a Blueprint level, so I consider it unsafe to use.

UObject: the more reliable way

I’ve now chosen to skip TScriptInterface and instead just hold a UObject*, because the object reference is the only bit of TScriptInterface which is reliable in cases where the interface is only implemented in Blueprints anyway.

UObject* SomethingInstance;

This way you can just assign the “raw” object pointer to this variable and use the standard “safe” way to call it, avoiding Cast<> as we previously discussed:

// I'm assuming here that we checked the object supported the interface before saving
// it to the variable; if not we should check for that as well as nulls
if (SomethingInstance)
    int Num = IDoSomeThings::Execute_GetNumberOfThings(SomethingInstance);

The End

So, that’s what I’ve learned about interfaces in Unreal in a mixed C++ / Blueprint environment in the last couple of weeks. I’d written it all down in my personal notes but came to the conclusion my usage must be pretty common, and those little gotchas seemed worth putting down somewhere public for others to potentially benefit from.

I’ve done my best but I’m not an expert, if I’ve misunderstood anything in this blog post, please do let me know on Twitter. 😉