Blueprint interface VS ActorComponent, The Good And The Bad

Long question short, are blueprint interfaces still relevant when we can extend Actor logic through ActorComponents?

Some time ago I started programming a system which lets Actors interact with the environment, so they can pick up ‘items’, read text written on walls or contained by book Actors, drink water and talk to NPCs. All combined, an interaction system can become quite large.

The logic for this interaction system was written in an ActorComponent, which is added to a Pawn so the Pawn can interact with the world, and through a Blueprint Interface which was added to any Actor that can be interacted with.

At first, I saw the benefits of Blueprint Interfaces. You can simply at that Interaction Interface to any actor in your level and send it a message “can you be eaten? can you be picked up? Can I talk to you?”

However, I realized this is rather ridiculous for the following reasons:

  1. We can simply add such an Interaction Component to any actor which is interactable and implement a response there.
  2. A Blueprint Interface method does not work with default property values.
  3. A Blueprint Interface clutters the class with unused and generic named methods.

Can we discuss any situation you found Blueprint Interfaces to be very useful and not replacable?

2 Likes

I code my games exactly like this as well. It becomes pretty easy to drop in all sorts of behavior into any actor. I first realized games in Unity are all built this way since that’s just how game objects are put together. I now hardly ever have any actor code aside from setting up interactions between the components.

I actually use interfaces a ton mostly to help find the component in O(1) time instead of calling, FindComponentByClass on the actor. All the interface does is it returns the appropriate component. A lot of my code just tries to call interface methods on different objects and if that interface is implemented and returns non-null, my code continues. Otherwise it assumes that behavior doesn’t exist on the object I’m calling the interface on. Only issue is if I forget to make an actor implement an interface and just add a component of some type, my behavior doesn’t work.

One great example is an inventory manager component that can exist in a character, treasure chest, dead body, random tree in the world. My character can walk up to any object and interact with its inventory component and see its contents. It’s super easy to just drop any actor into the world, and configure what inventory items it has in its inventory.

I can also very easily make interactable “Usable” objects by having a User component and a Usable component. User components keep track of Usable components that can currently be “Used”. This is done by knowing which ones are currently overlapping with collision geometry and priority is given to the “Usable” if the owner actor’s GetActorEyesViewpoint is aiming at it, otherwise it choses the closest one. I can make my player hit the “Interact” button and it’ll take care of calling OnUsed on some appropriate UsableComponent. The Usable component just has a OnUsed callback that the actor can implement however it sees fit. It’s very easy to build a level, throw in a random actor that has a usable component, and hook up a level blueprint to do something when that Usable is triggered by a User.

I even do this for tiny things like controlling how my first person character’s hands animate. I used to put things like that into the ACharacter subclass, but now I really put most behavior into components. I try to isolate specific behaviors into components and have that component only do that thing. It does get a bit hard though since sometimes components need to talk to other components, or it’s actually not that obvious how to split the behavior up and I end up doing a somewhat bigger component that does more things than it should. After a while I find ways to refactor it.

My code is also currently not built for multiplayer, but I think it’ll be much easier to go through component by component and add proper replication and other things when the time comes, since I have all of my behavior already split up into tiny manageable chunks.

One thing I used an interface for is an idea I got from Unreal Tournament source. There’s a Reset interface. The start of a match or round fetches all instances of things that implement the reset interface to put the game back to normal. This can help with games like Counterstrike where you don’t want to reload a map every round. Things like bullets, shell casings, impacts, can kill themselves on reset so they disappear. Respawnable items, etc… can ensure they’re back in their initial state.

10 Likes

Refactoring is not a bad thing. When a project grows any categorized piece of logic will become a different version of its old “piece in the puzzle” :slight_smile: . I feel like implementing logic through ActorComponents instead of Interfaces is a lot cleaner.

One great example is an inventory manager component that can exist in a character, treasure chest, dead body, random tree in the world. My character can walk up to any object and interact with its inventory component and see its contents.

This is what I do as well, an inventory system is simply too big to be managed through a BP Interface. Whenever I implement an interface for something what (should be?) a component I feel like my code is dirty.

Especially if inventory code, or other things, become incredibly complex. My inventory manager has become a fairly complicated thing that’s more than just storing an array of objects. Gotta track stack counts, maximum amounts, distributing stack counts among existing items when adding, removing etc…

Side note, depending on your project needs, but in my system I realized “Item Stacks” are normally a filter rather than a way of storing the data since you can stack an item by any type of state (is it equipped? is it equal (to what point)? ). Realizing what is a filter or just a visual representation makes inventory management easier.

2 Likes

The setup I’m using is pretty much the same as yours.
Regarding the dependencies between components, this is another benefit of interfaces. An interface can hide the fact that an implementation of the interface actually depends on another component. Only if that other component would become part of the interface e.g. via input or output parameter that would no longer be the case. If you make sure not to add such things to your interfaces this can help reduce large blueprint dependencies because the chain stops at the interface instead of pulling in everything the other component needs. In your example rather than directly returning the component from the interface used to get the component in O(1), you would instead return the interface that is implemented by the component.

4 Likes

That could actually result in having to write more boilerplate code where I have to define the interface in one file, and then make sure another component implements that interface. In C++ you would now have 3 files to keep in sync, the interface .h file, the component .h file, and the .cpp file.

If you think about it, theoretically it shouldn’t really matter whether a variable references an interface or a pointer to a base class of a component that has the same functions. That base class can be abstract too. Children of the class can always override things if needed.

In the case of my usable component example, all my usable component does is call broadcast on a multicast delegate, so how actors actually implement being usable is still very easy to customize.

Yes there are some downsides to that additional interface as well. I guess if we’re talking about C++ that additional interface is unnecessary and using the other component directly is fine. But for Blueprint which I assumed to be the relevant question for @Roy_Wierer.Seda145 that’s another story. I’ve seen at least one project that was unable to be packaged because of nasty blueprint dependencies that essentially ended up referencing the entire project. The reason for that was that there were so many recursive calls for loading assets during the packaging process that the stack ran out of memory and the process crashed. At first only sometimes depending on the order things were loaded in but the more stuff was added this happened more frequently and eventually it never succeeded anymore. Until the blueprints were refactored increasing the maximum stack size of the executable fixed this as well. The components were created in blueprint in that project and didn’t have any special native base classes, they were derived directly from ActorComponent.

Components are great if you only ever need one implementation of a particular calling convention.

Interfaces are great if you need many different implementations of the same calling convention. If you have sound effects, particle effects, animation effects, and other kinds of effects that all have a StartEffect() interface, you can’t easily implement that with a component. You’d have to make an EffectStartComponent that you added to each effect, and then bind an event dispatcher to the actual class-specific effect starting function, which is a lot more work than just defining and implementing the interface for StartEffect().

The C++ toolbox is big. Blueprints add an even bigger set of tools. As programmers, its our job to choose the right tool for the job in each instance. And, sometimes, the job changes, and thus we need to put down one tool, and pick up another tool. Being too overly attached to any one particular tool is seldom helpful.

12 Likes

I use components when I want to use the same logic and interfaces when I just want some communication without having to multicast.
I think that one of the best uses of interfaces is precisely to communicate with the owner of the component.
I would recommend this video from the unreal learning portal “Breaking Down the Components of Gameplay”

5 Likes

Oh wow that’s definitely something to watch out for.

I do run into an issue quite a bit where I work on a project for a while without trying to package it, and things go horribly wrong when I try to for the first time in months.

Makes me so glad I am rewriting everything to c++ now. What a pain BP became, the bigger the project the worse it gets :slight_smile:

Lately I have been thinking about experimenting with UObjects instead of Blueprint Interfaces as well for some / most situations.
I know there is no direct replacement for these Interfaces, but take the following comparison:

A text written on a wall, item or painting can be read by a player by interacting with it:

Solution A: The easiest way to retrieve “readable data (struct)” is by implementing a Blueprint Interface with the method “GetReadableData” which any Actor can implement to send or not send a readable response to the caller.

Solution B: A more complex solution is to add a UObject which acts like the interface, but is reusable. We can make an abstract base class “BP_ObjInterface_Interaction_Readable” in which we define a method “GetReadableData” which we can override on deriving “object interfaces” providing custom but re-usable logic.

I found this a particularly interesting concept when thinking about how to make a complex camera system which involves transitioning through multiple camera modes (Think Nier Automata, going third person to sidescroller to anything else), in which case you could inject UObjects into the camera manager which act like a chain of filters for camera transition and user input. Then I thought, isn’t this the most dynamic way to set up any system with re-usable logic? That’s where the question came from :slight_smile:

I’ve ended up using UObjects quite a bit in my UI code since UMG widgets aren’t actors you can add components to. There’s so much reusable code I add to various UMG widgets. It takes some boilerplate blueprint to hook things up but once it works it’s great.

1 Like

I use the HUD class to store components for the UMG widgets it adds to the viewport. In c++ this also helps with garbage collection management because Actors automatically reference their ActorComponents, so there is no need to store those components in UPROPERTY.

I have a question on the “why use interfaces with components” thing.

I have an actor with only a general actor object reference to another blueprint it wants to talk to. On that other blueprint, I have a component with the behavior I’m after.

That’s a use case for interfaces and components, right? Because unless I add the interface to the component, the actor with the generalized object reference isn’t allowed to say anything to that reference, right? (Without casting, at least…)

So is this just a case of either using interfaces or just allowing castings to fail?

That is not quite right. You could call “Find component of class” on the actor reference, that leaves you with the component on the other actor which you are after. You don’t need an interface to do so because you can find a component on any actor. An interface would be used in a different case where you need an implementation of “DoStuff” on completely unrelated classes / which don’t have such functionality otherwise. An interface allows you to call a method on that interface on any class you attach it to, but that method has to be implemented by those classes. As you can see here the AActor class already has a method to find components.

1 Like

I think of Actor component as “same behavior among many” and interface as “unique behavior from same event among many”

These days though I have moved almost entirely to an architecture where all public data is held in actor components attached either to controller or game mode (e.g. they are globally accessible). I call these “state components”.

Then there are other actor components also attached to the controller/gamemode I call “systems” which run logic to read/write to the state components.

Systems can read/write to the state components but never communicate system to system. Events in the level are broadcast via event dispatches, which are called from another actor component I call the “event relay”. Any system can bind to this components events. Basically a central radio station where anything important in the game is broadcast.

It’s loosely inspired by ECS architecture. The benefit is that class to class communications always follows the same pattern in every instance and everything stays decoupled by default. It just makes code much more manageable for a solo-dev because it eliminates having to figure out data flow on a case by case basis, you don’t need any unique tricks to maintain decoupling, and code stays highly modular.

Unreal has a ton of great classes and tools but if each one brings it’s own host of caveats such that you have to remember a five step process to do any little thing, I find it makes development for the soloist unsustainable. So a system that just reuses the same tools over and over to accomplish virtually every task has been the goal I’ve been working towards.

4 Likes

Hi! I’m currently trying to find the best approach for such problems myself. Is this roughly the setup that you proposed here?
Interface:

UINTERFACE(BlueprintType)
class UMyActorInterface : public UInterface
{
	GENERATED_BODY()
};

class MYPROJECT_API IMyActorInterface
{
	GENERATED_BODY()

public:
	virtual void DoSomething() = 0;
};

Component:

class MYPROJECT_API UMyComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	void DoSomething(); // Actually contains the functionality I want to call
};

One of the actors that has the Component:
.h

class MYPROJECT_API AOtherActor : public AActor, public IMyActorInterface
{
	GENERATED_BODY()

public:
	virtual void DoSomething() override;

private:
        UMyComponent* MyComponent;
};

.cpp

void AOtherActor::DoSomething()
{
        MyComponent->DoSomething();
}

Manager class that wants to call functionality of the component that OtherActor might have:
.h

...
    private:
        IMyActorInterface* OtherActor;
...

.cpp

...

// Get Possible Actor I want to call functionality on
// Check if Possible Actor implements the Interface
// Maybe only updated rarely
OtherActor = GetClosestActorImplementingIMyActorInterface() 

...

// Functionality called in every Tick
if (OtherActor!= nullptr)
{
    OtherActor->DoSomething();
}

...

This would require a lot of boilerplate code, yes. But if I dont use a interface on the OtherActor would I not need to call GetComponentByClass and then try to cast to my type of component everytime? With this I approach I make sure that the other actor that I want to call functionality on has the needed component. And thus cast to the interface only once and not every time. Also everytime I call DoSomething() on the Interface the code looks much cleaner because no further casting is required. Is there anything I am missing?
Thank you!

Yes, but casting is made simple:

UYourComponent* Comp = Actor->FindComponentByClass<UYourComponent>();

No you make sure it has the interface. It doesn’t mean it implements the component, you’d still have to test for a component if you need access to one, then check if it’s valid.

There’s also this annoyance when using interfaces with blueprints:

// First check if the interface was implemented in c++ or BP. If it was only implemented in BP a c++ cast would return nullptr.

	bool bCanBeGrabbed = false;
	if (OtherOwner->Implements<UGrabInteractionInterface>()) {
		bCanBeGrabbed = IGrabInteractionInterface::Execute_CanBeGrabbed(OtherOwner, InPrimitiveComponent);
	}
	else if (const IGrabInteractionInterface* OtherInterface = Cast<IGrabInteractionInterface>(OtherOwner)) {
		if (OtherInterface) {
			bCanBeGrabbed = OtherInterface->CanBeGrabbed(InPrimitiveComponent);
		}
	}

You forgot to mark this UPROPERTY() which is required for the garbage collector:

4 Likes