Timothy Marks Memoji

Timothy Marks

@imothee

Hi, I'm Tim, an engineer, founder, leader and hacker. Here you'll find my articles, apps, projects and open source stuff

Drink_BP

Unreal Interfaces

How to implement Interfaces in Unreal using Blueprints or C++

Interface Off

If there's one thing I would highly recommend to everyone beginning their Unreal development using Blueprints or C++ is to heavily understand how to use, implement and consume interfaces.

Interfaces in Unreal Engine | Unreal Engine 5.1 Documentation

Interfaces let you define a common set of established functions that make it far easier to manage and call functions on other blueprints and classes without the need to first cast the object. Interfaces can make it easier to define consistent interfaces across items or to make it easier to call functionality that extends a base Unreal object.

Making up an (inter)face

Let's talk through the two main useful ways to setup and consume an interface.

In this scenariom we will have a common set of functions to open and drink various kinds of drinks. Now, we could have done this by creating a Drink class and having each kind of Drink extend the base one but I find interfaces much cleaner, earier to use and requires way less casting and class checking.

Other ways of using interfaces can be to define functionality on a PlayerController, make refactoring and moving functions out of classes or just simplify the call chain and remove casting. Since interfaces work and let you easily call functions between Blueprints and C++ I will walk through both examples.

Defining an Interface

C++

C++ Interfaces only need a header file that defines the interface functions so let's get started by creating

Public/Interfaces/DrinkInterface.h

The UDrinkInterface is an empty class only used for Unreal class reflection.

Inside of the IDrinkInterface we will be creating two functions

  • An IsDrinkOpen function that returns whether the drink is open or not
  • An OpenDrink function that will open the drink and return nothing
  • A Drink function that will try and drink the drink and return whether you were successful and whether the drink is finished.
// This work is licensed under a Creative Commons Attribution 4.0 International License https://creativecommons.org/licenses/by/4.0/
#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "DrinkInterface.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UDrinkInterface : public UInterface
{
  GENERATED_BODY()
};

// This class contains all of your common/defined functions
class SERFSUP_API IDrinkInterface
{
  GENERATED_BODY()

public:
  // A function to check if the drink is open
  // BlueprintNativeEvent can be implemented in C++ by overriding a function with the same name, but with the suffix _Implementation
  // BlueprintCallable let's the function be called in C++ or Blueprint using a reference to an object that implements the interface
  // Category lets you group like functions in Blueprint interface list
  UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = Drink)
  bool IsDrinkOpen();

  // A function to open the drink, doesn't return anything
  UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = Drink)
  void OpenDrink();

  // A function to drink the drink
  // Returns two variables (through multi-variable output rather than function return)
  // Success will be true if the drink is open and there is some liquid left in the drink
  // DrinkFinished will be true if the drink is already empty or empty after taking the drink
  UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = Drink)
  void Drink(bool &Success, bool &DrinkFinished);
};

Blueprint

Or as a Blueprint, we will create a new BlueprintInterface named "BPI_Drink". We will then create three functions as above.

Implementing an Interface

Like Chumbawumba in Tubthumping we're going to drink a few drinks and set them up through the DrinkInterface.

Firstly we're going to implement a WhiskeyDrink that implements DrinkInterface in C++.

Public/Drinks/WhiskeyDrink.h

We will be extending the AActor base class and define that AWhiskeyDrink extends the public IDrinkInterface.

// This work is licensed under a Creative Commons Attribution 4.0 International License https://creativecommons.org/licenses/by/4.0/
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Interfaces/DrinkInterface.h"
#include "WhiskeyDrink.generated.h"

UCLASS()
class SERFSUP_API AWhiskeyDrink : public AActor, public IDrinkInterface
{
  GENERATED_BODY()

  // Sets default values for this actor's properties
  AWhiskeyDrink();

protected:
  // Called when the game starts or when spawned
  virtual void BeginPlay() override;

public:
  // Called every frame
  virtual void Tick(float DeltaTime) override;

public:
  // Track the open state of the actor
  bool bIsOpen;
  // Track the number of sips in a drink
  int Sips;

public:
  // DrinkInterface
  // Each Implemented function needs to be registered here
  // Please note the _Implementation nomenclature and the override required here
  // This markup tells Unreal that these functions are implementing an interface
  bool IsDrinkOpen_Implementation() override;
  void OpenDrink_Implementation() override;
  void Drink_Implementation(bool &Success, bool &DrinkFinished) override;
};

Then in the corresponding CPP file

Private/Drinks/WhiskeyDrink.cpp

We will implement the 3 Drink interface functions

// This work is licensed under a Creative Commons Attribution 4.0 International License https://creativecommons.org/licenses/by/4.0/
#include "Drinks/WhiskeyDrink.h"

// Sets default values
AWhiskeyDrink::AWhiskeyDrink()
{
  // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
  PrimaryActorTick.bCanEverTick = true;

  // Set IsOpen to true by default
  bIsOpen = true;
  // A whiskey cup has 4 sips in it
  Sips = 4;
}

// Called when the game starts or when spawned
void AWhiskeyDrink::BeginPlay()
{
  Super::BeginPlay();
}

// Called every frame
void AWhiskeyDrink::Tick(float DeltaTime)
{
  Super::Tick(DeltaTime);
}

/* DrinkInterface */
bool AWhiskeyDrink::IsDrinkOpen_Implementation()
{
  return bIsOpen;
}

void AWhiskeyDrink::OpenDrink_Implementation()
{
  bIsOpen = true;
}

void AWhiskeyDrink::Drink_Implementation(bool &Success, bool &DrinkFinished)
{
  // Set whether the drink is finished
  DrinkFinished = Sips == 0 ? true : false;

  if (!bIsOpen || DrinkFinished)
  {
    Success = false;
    return;
  }

  Sips -= 1;
  DrinkFinished = Sips == 0 ? true : false;
  Success = true;
  return;
}

And we'll be implementing a Soda Drink as a Blueprint just to show you both ways to implement an Interface. To start with we'll create a Blueprint extending Actor and name it BP_Soda. To tell the Soda actor it implements DrinkInterface, we open the Blueprint, go into Class Settings and add the DrinkInterface as an interface.

Adding the DrinkInterface will add 3 stubbed functions to the Interfaces section of the Blueprint.

Let's add a Boolean IsOpen variable and an Intege Sips variable as well. Set IsOpen to false and let's set Sips to 10 for the Soda bottle.

If we double click on the Is Drink Open function in the iterface we will open the Blueprint editor where we can drag in the IsOpen variable and setup the function there.

By default functions that have a return value will be created as a nested function while void functions will be added as an event on the EventGraph. If we double click OpenDrink we will instead get a EventOpenDrink node that we can create a subfunction to call or do the logic inline. For simplicity lets implement Open inline.

Lastly lets setup the same logic in the C++ for taking a drink.

Calling an Interface

So now we have two objects that implement the DrinkInterface. We're not going to stub an entire setup to take a drink on the action key but let's assume we have a binding in the PlayerCharacter.

C++

Without an Interface, if we extended the base Drink object or implemented the functions directly on the object we'd call them like this.

// This work is licensed under a Creative Commons Attribution 4.0 International License https://creativecommons.org/licenses/by/4.0/
ASerfsUpCharacter::ASerfsUpCharacter()
{
  AActor *Actor = CreateDefaultSubobject<AWhiskeyActor>(TEXT("WhiskeyActor"));
}
void ASerfsUpCharacter::Drink(const FInputActionValue& Value)
{
  AWhiskeyActor *Whiskey = Cast<AWhiskeyActor>(Actor);
  if (Whiskey)
  {
    if (!Whiskey->IsDrinkOpen())
    {
      Whiskey->OpenDrink();
    }
    bool Success;
    bool DrinkFinished;
    Whiskey->Drink(Success, DrinkFinished);
    // Do stuff based on the success of the drink and finishing the drink
  }
}

This is fine, we just have to in this case import the WhiskeyDrink.h class and do the cast each time we want to use the specific Drink functions. This is fine with one type of Actor or when we have a high likelyhood that the Actor will be a WhiskeyDrink. However if we have a lot of different Drink types this will get messy. Instead let's use the interface instead.

There's a few ways you can check if a class implements an interface, we will show all 3 but I prefer using the Object->Implements method.

To call a function on an interface in C++ you can use the static Execute_ function, which takes the object you're trying to call the function against as the first param and the rest of the function params afterwards.

// This work is licensed under a Creative Commons Attribution 4.0 International License https://creativecommons.org/licenses/by/4.0/
#include "Interfaces/DrinkInterface.h"

void ASerfsUpCharacter::Drink(const FInputActionValue& Value)
{
  // It's important to note the UDrinkInterface being used here
  if (Actor->Implements<UDrinkInterface>())
  // Or we could use this instead
  if (Actor->GetClass()->ImplementsInterface(UDrinkInterface::StaticClass()))
  // Or we could try casting to the Interface, notice we cast to the IVersion
  IDrinkInterface *Drink = Cast<IDrinkInterface>(Actor);
  if (Drink)

  // But lets use my preferred way
  if (Actor->Implements<UDrinkInterface>())
  {
    if (!IDrinkInterface::Execute_IsDrinkOpen(Actor))
    {
      IDrinkInterface::Execute_OpenDrink(Actor);
    }
    bool Success;
    bool DrinkFinished;
    IDrinkInterface::Execute_Drink(Actor, Success, DrinkFinished);
    // Do stuff based on the success of the drink and finishing the drink
  }
}

Now no matter what Drink we have as Actor, we can call all the functions against it. How nice is that?

What's even better is that calling an interface against an object that doesn't implement it is a void function so you can jsut assume you have an interface and call against it without repurcussions.

BluePrint

Here we're showing that we can create a Soda Actor and save it to an Actor object reference. Then (here in on Tick) we can call every function against the Actor without ever having to cast it. Interfaces make calling functions against base objects a breeze!

December 29, 2022

0

🕛 8

© 2022 - 2023, Built by @imothee