Skip to content

Unreal Engine Interfaces & Interaction

Table of Contents

Introduction

In this post I'll look at implementing interfaces in Unreal Engine with C++ to create an interactable object. I will be using JetBrains Rider as my IDE for this guide.

This post is a continuation of my previous post on creating a first-person player character in Unreal Engine 5 with C++ & Enhanced Input, and part of my series of documenting what I learn while creating a hotdog truck simulator game in Unreal Engine 5.

Creating an interface

What is an interface?

An interface is a way of defining a common function that multiple classes can share.

Interfaces in Unreal Engine allow communication to many different object types (think doors, pickups, etc) without needing to know what object is being interacted with, only that it can be interacted with.

For this example, I will be creating an interactable object that logs a message when it is interacted with.

Creating the interface in C++

My interface will be called Interact, and will contain a single interact function, Interact.

I can start by creating a new C++ class in our project that inherits from the UInterface class. Creating New Interface Class in Rider

I can then define our interact function in the header file.

UInteract vs IInteract

When creating our Interact interface, two classes are created - UInteract and IInteract.

  • UInteract is a UObject derived class that is used by the Unreal Engine reflection system and is required for interfaces to be recognized by the engine.
  • IInteract is the actual interface class that contains our interface's functions and is the class that other classes must implement to use the interface.

BlueprintNativeEvent

As discussed in my previous post, BlueprintNativeEvent allows us to provide a default implementation in C++, while still allowing the function to be overridden in Blueprints if needed.

I think that it's a good idea to use BlueprintNativeEvent for interface functions, as it allows the functions to be overridden in Blueprints, which allows for more designer friendly and rapid prototyping.

c++
UINTERFACE()
class UInteract : public UInterface {
 GENERATED_BODY()
};

class FASTFOODGAME_API IInteract {
 GENERATED_BODY()

public:
 UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = Interact)
 void Interact(AActor* Interactor);
};

Creating an interactable object

For this example, I will be creating a PickupItem actor class that implements the Interact interface and logs a message when it's interacted with.

I can start by creating a new actor C++ class. Creating New Actor Class in Rider

I then have to make sure that I am including my interface's header file, and that my class inherits from the interface.

c++
#include "FastFoodGame/Interfaces/Interact.h"

_Implementation

As mentioned in my previous post, when using BlueprintNativeEvent, the implementation of the function must be provided with the _Implementation suffix.

The Interact function in the PickupItem class can be overridden by providing an override implementation of the Interact_Implementation function.

c++
UCLASS()
class FASTFOODGAME_API APickupItem : public AActor, public IInteract {
 GENERATED_BODY()

public:
 // Sets default values for this actor's properties
 APickupItem();

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

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

 virtual void Interact_Implementation(AActor* Interactor) override;
};

I can then provide the implementation in the C++ source file to log a message.

c++
void APickupItem::Interact_Implementation(AActor* Interactor)
{
 IInteract::Interact_Implementation(Interactor);

 UE_LOG(LogTemp, Log, TEXT("Interacted with"));
}

Getting the player character to interact

Now I need to set up my player so that it can interact with objects that implement the Interact interface.

In my previous post, I set up a first-person player character with C++ and Enhanced Input. We can add a new input action for interacting, I will be binding this to the Left Mouse Button. You can see how to set up input actions and mapping contexts in my previous post here.

Component based approach

Rather than adding the interaction logic directly to the player character class, I will be using an actor component to handle the interaction logic.

This keeps the player character class cleaner, and also allows for the interaction logic to be reused in other classes should it be needed.

Creating interaction component class in Rider

I'm going to create a public function to attempt an interaction, which will be tied to the input action. I will also be exposing an interaction distance property so that it can adjusted in the editor - remember that we want to keep our systems designer friendly.

c++
UFUNCTION()
void AttemptInteraction();

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Interaction")
float InteractionDistance = 250.0f;

The implementation of the AttemptInteraction function will use a line trace to check for interactable objects in front of the player character. If an interactable object is found, it will call the Interact function on that object.

GetActorEyesViewPoint

GetActorEyesViewPoint() ensures that our line trace will start from the player's view, taking the camera position and rotation into account.

This function is an output function - as in, the parameters are passed in by reference, and the function will modify the values of the parameters. The values of startLocation and startRotation are updated to hold the appropriate values.

c++
void UInteractionComponent::AttemptInteraction() {
 FHitResult hitResult;
 
 AActor* owner = GetOwner();
 FVector startLocation;
 FRotator startRotation;
 owner->GetActorEyesViewPoint(startLocation, startRotation);
 FVector endLocation = startLocation + (startRotation.Vector() * InteractionDistance);

 // Set up parameters to ignore the owner actor in the line trace
 FCollisionQueryParams params;
 params.AddIgnoredActor(owner);

 // DrawDebugLine(GetWorld(), startLocation, endLocation, FColor::Red, false, 2.0f);
 bool wasAnythingHit = GetWorld()->LineTraceSingleByChannel(hitResult, startLocation, endLocation, ECC_Visibility, params);
 if (!wasAnythingHit)
  return;

 // Call the interact function on the hit actor if it implements the interface
 AActor* hitActor = hitResult.GetActor();

 bool doesActorImplementInterface = UKismetSystemLibrary::DoesImplementInterface(hitActor, UInteract::StaticClass());
 if (!doesActorImplementInterface)
  return;

 IInteract::Execute_Interact(hitActor, owner);
}

Returning to the player character, the attempt interact function in the interaction component can now be bound to the interact input action. I created a private property to hold a reference to the interaction component.

c++
UInteractionComponent* InteractionComponent;

Binding the input action in SetupInputComponent requires us to first get a reference to the interaction component. It is okay to perform this here as it will only be called once when input is being set up.

c++
 if (IAInteract) {
  InteractionComponent = FindComponentByClass<UInteractionComponent>();
  if (InteractionComponent) {
   enhancedInputComponent->BindAction(IAInteract, ETriggerEvent::Started, InteractionComponent, &UInteractionComponent::AttemptInteraction);
  } else {
   UE_LOG(LogPlayerInput, Error, TEXT("No interaction component was found on the player character"));
  }
 }

Testing the interaction

With everything set up, I can now add a PickupItem actor to the level, and test the interaction.

When I play the game and look at the pickup item, pressing the Left Mouse Button will log "Interacted with" to the output log. Testing interaction in Unreal Editor

Taking this further

This interaction system could be expanded in many ways, such as:

  • adding highlighting to interactable objects when the player looks at them
  • adding a UI prompt to indicate that the player can interact with an object
  • adding different types of interactions for different objects, and showing different prompts based on the type of interaction available

The interact function can also be overridden in Blueprints, why not try overriding the function in Blueprints to do something different?

My Reflection

Learning to create interfaces via C++ in Unreal Engine has been really useful and will be beneficial going forward to ensure I maintain good communication practices between different systems in my game.

Using actor components is also something that I want to continue to explore, as it keeps code not only cleaner and more manageable, but also allows for reusability where applicable.

I will be adding more feedback to the interaction system in the future, incorporating UI prompts and highlights to improve the user experience.