April 8, 2021
One of the oft-quoted pitfalls of the inheritance model is multiple inheritance, which can lead to memory overlaps, unexpected behavior, and hard to track bugs (although not everyone agrees). However, multiple inheritance using interfaces, classes which contain only overridable functions, can ameliorate many of these concerns while providing flexibility and code decoupling. For this reason, the use of multiple inheritance with interfaces abounds.
In fact, Unreal Engine 4 does not permit multiple inheritance except via interfaces. Yet the syntax for using interfaces with the Engine can be clunky and easy to forget. This article will provide a comprehensive overview of interfaces in the Engine and describe the various methods for implementing such interfaces.
An interface provides a way to ensure that potentially unrelated classes implement a common functionality. For example, consider a game like Grand Theft Auto V where countless objects can be interacted with, from a car, to a telephone, to a helicopter. When the player overlaps one of these objects, one way to interact is to check whether the object is part of a predefined set of classes (Car
, Phone
, Helicopter
) and then call the appropriate interaction method if it is.
This can get unwieldy quickly as you add classes though. As an initial matter, you have to keep that list of interactable classes constantly up to date. In addition, each class may have a different interact method signature, requiring different inputs or returning different values. To top it off, your cost of determining whether a class is interactable increases linearly - if the list contains fifty classes, you may have to check all fifty before determining that a class is or is not interactable.
This can be solved easily by creating an Interactable
interface with, at a minimum, an Interact
method. Now, on overlap, all we need to do is check whether the class implements Interactable
. If it does, call the Interact
method on it. The result is a much neater, less coupled, easier to maintain solution.
Interfaces in Unreal Engine 4 can take many forms, depending on whether the interface or its functions are intended to be used in C++, in Blueprints, or both.
Every interface must have at least two elements: a UInterface
and an IInterface
. The UInterface
should look like this:
UINTERFACE()
class UInteractableInterface : public UInterface
{
GENERATED_BODY()
}
Here, UInteractableInterface
derives from UInterface
, the base class for Engine interfaces (located at UObject/Interface.h). The name of the class ends with Interface by convention, but this is optional. Finally, as with other reflected classes, the body of the class contains a GENERATED_BODY()
macro which will be used by the UnrealHeaderTool to create reflection data.
The IInterface
should look like this:
class IInteractableInterface
{
GENERATED_BODY()
public:
// ... functions go here
}
The name of the IInterface
must exactly match the name of the UInterface
but with an I prefix. This class must also contain a GENERATED_BODY()
macro. Then, define desired functions. Note that neither the UInterface
nor the IInterface
may contain UProperty
s. Attempting to include such a member will result in the following error:
'Member variable declaration' is not allowed here
You can get away with including non-UProperty
properties, but you should avoid doing so, as it defeats the principle of making interfaces functionality-only.
Now to the implementation.
To make an interface exclusively implementable in C++, add the CannotImplementInterfaceInBlueprint
meta specifier to your UInterface
, like so:
UINTERFACE(meta=(CannotImplementInterfaceInBlueprint))
In this type of interface, you can use pure and defined virtual functions, but you may not use BlueprintImplementableEvent
or BlueprintNativeEvent
functions.
virtual void DefinedVirtualFunction() {};
virtual void PureVirtualFunction() = 0;
True to its name, this interface cannot be implemented in Blueprints. However, it can be called from Blueprints.
UFUNCTION(BlueprintCallable)
virtual void ExposedVirtualFunction() {}
An interface allows implementation in C++ and Blueprints by default. Official documentation suggests that you should also decorate the UInterface
with the Blueprintable
specifier, but as of 4.26, behavior with or without this specifier appears identical.
UINTERFACE(Blueprintable)
In this type of interface, you can use pure and defined virtual functions as before, but such functions cannot be BlueprintCallable
. However, you can also introduce BlueprintImplementableEvent
s, which can be implemented only in Blueprints, and BlueprintNativeEvent
s, which can be implemented in both C++ and Blueprints. Both types can be BlueprintCallable
.
UFUNCTION(BlueprintImplementableEvent, BlueprintCallable)
void BPFunction();
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void BPNativeFunction();
To implement an interface natively, simply inherit from its IInterface
variant and define its respective methods.
UCLASS()
class UImplementor
: public UObject
, public INativeInterface
, public IMixedInterface
{
GENERATED_BODY()
public:
void DefinedVirtualFunction() override
{
// do something
};
void PureVirtualFunction() override
{
// do something else
}
void BPNativeFunction_Implementation() override
{
// do something native
}
};
Notice that with BlueprintImplementableEvent
overrides, you must add the _Implementation
suffix.
To implement an interface in Blueprints, navigate to the Class Settings tab in the toolbar at the top of the Blueprint editor and look for the Interfaces panel.
There are two methods for determining if a class is an interface, and which one you use depends on how the interface was declared. If the interface is native-only (cannot be implemented in Blueprints), simply cast the target class to the desired IInterface
, and then use the interface like any other class.
void Foo(UImplementor* PotentialInterface)
{
if (INativeInterface* Interface = Cast<INativeInterface>(PotentialInterface))
{
Interface->PureVirtualFunction();
}
}
If the interface is mixed, the cast will fail for Blueprint classes that implement the interface. Instead, we ask the class whether it implements the UInterface
using the Implements
template method. Then we call the generated static proxy method of the function we want to call from IInterface
, passing the object that implements the interface as the first parameter. This function will be prefixed with Execute_
.
void Foo(UImplementor* PotentialInterface)
{
if (PotentialInterface->Implements<UMixedInterface>())
{
IMixedInterface::Execute_BPNativeFunction(PotentialInterface);
}
}
And that's it. Nailing the syntax is no cakewalk, but once you get the hang of it, interfaces will be a powerful tool in your Unreal Engine 4 toolbelt.