Blueprint interface VS ActorComponent, The Good And The Bad

That’s one way to do it, although I’d not recommend duplicating the implementation of IMyActorInterface in AOtherActor even if you just pass the calls to MyComponent. If you ever had to change the interface you’d not only have to update the UMyComponent implementation but also pass it through in AOtherActor.

What I was proposing probably looks more like this:

  • keep the U/IMyActorInterface
  • add another interface (which eliminates the need to cast or FindComponentByClass etc.)
UINTERFACE(BlueprintType)
class UMyActorInterfaceProviderInterface : public UInterface
{
	GENERATED_BODY()
};

class MYPROJECT_API IMyActorInterfaceProviderInterface
{
	GENERATED_BODY()

public:
	UFUNCTION(...)
	TScriptInterface<class IMyActorInterface> GetMyActorInterface();
};
  • change MyComponent like this:
class MYPROJECT_API UMyComponent : public UActorComponent, public IMyActorInterface, public IMyActorInterfaceProviderInterface
{
	GENERATED_BODY()

public:
	// actual implementations for IMyActorInterface functions
	void DoSomething() override;

	// actual implementation for IMyActorInterfaceProviderInterface function
	TScriptInterface<IMyActorInterface> GetMyActorInterface_Implementation() override
	{
		return this;
	}
};
  • change OtherActor to tis
class MYPROJECT_API AOtherActor : public AActor, public IMyActorInterfaceProviderInterface
{
	GENERATED_BODY()

public:
	// actual implementation for IMyActorInterfaceProviderInterface function
	TScriptInterface<IMyActorInterface> GetMyActorInterface_Implementation() override
	{
		return MyComponent;
	}

private:
	UPROPERTY(...)
	UMyComponent* MyComponent;
};

From that point on you can always just pass the ProviderInterfaces around until you actually need to call functions from the provided interfaces. Doing so is as simple as calling Get... and then the desired function. The ProviderInterfaces will always have clean asset references as long as you keep the references of the provided interfaces clean. And if you only use ProviderInterfaces for inputs/outputs besides the built-in types in those that shouldn’t ever become a problem.

1 Like

First of all, thank you guys! Unfortunately I don’t have enough time to work on this very often at the moment, sorry for the late answer.

This made it more clear. However now I wonder what I would do in case everything stays in C++ except AnotherActor. Maybe it is easier if I just explain my current system I am trying to build instead of abstracting it into the example above.

I will be building most things in C++ but not all. I need a extendable Grid Building System that can place Actors. New types of Actors that can be placed should be very easily added.

I have a Grid Class C++ Actor that will exist only once in the scene.

I have a PlaceableComponent C++ Component that has

  • size property (amount of local cells it occupies)
  • orientation property
  • a rotate function that cycles the orientation
  • necessary ressources property

The Grid Class handles

  • the actual placing and removing of actors that have a PlaceableComponent
  • the calculation behind what global cells will be occupied depending on location, orientation and PlaceableComponent size property
  • the calculation behind what cells are at what location
  • storage of what cells are occupied or blocked

Lets say the Grid Class has a member variable called CurrentPlaceable. This should be a Object that has the PlaceableComponent. Maybe its a C++ Class, a Blueprint or a StaticMeshActor which has the PlaceableComponent added to it.

What do you guys think would be the best option, to:

  • keep the functionality and properties of the PlaceableComponent in a component (instead of using inheritance) (const properties may later be moved to a data table)
  • have the CurrentPlaceable member variable accept ANY UObject (or AActor I suppose) with the only requirement being it having the PlaceableComponent
  • make the best compromise between boilerplate code, lots of casting, and delegating calls

I suppose, like @Roy_Wierer.Seda145 proposed, just using FindComponentByClass and the CurrentPlaceable member variable being of type AActor* would suffice. Being used to utilizing inheritance everywhere, I however still find myself to be dissatisfied with all of the casting and if-checks.

Thanks again for the input @UnrealEverything . If you have any more thoughts about this, I would appreciate any suggestions.

1 Like

Components can hold data and have default implementations. Interfaces can only send messages, so they will seem inferior.

One advantage interfaces have is that they can be added to components.

If you have a large device with a lot of different levers to pull, making the levers components with the interface added to them will make the whole implementation neater.

If you only had a single component to handle the interactivity it would also have to include the logic for the complete system, for example determining which lever (if any at all) the player is trying to pull.

It’s also not really an either-or question. Using one does not prevent you from using the other. You can use both.

“Interaction” is big enough to write an interface for . method Use(), does it pull / activate / grab?
“Pull” is either part of that or tiny enough to just write an implementation on a component. Components are optimally kept as tiny as possible and don’t add any logic they won’t use themselves. It’s a minimalistic implementation. That could be just to broadcast “I am pulled” when it is asked to. Maybe you want to store a reference to another object there which needs to respond visually, like a light or a lever. That would be the object you put an interface on and call “VisualizeActivation()” on, regardless of what that might mean in specific.

If I need any control over multiple “PullComponents” I’d usually register them to a manager, such as “PullManagerComponent”. Such a manager usually does not do anything besides being a utility to provide access to registered components such as “reset all levers”. Makes it easier to manager them from the owning Actor.

More than often there is no proper default implementation on what to do with levers. Think of a puzzle game in which each puzzle actor is different. It is the owning actor which implements that logic. The components simply broadcast delegates when X is pulled or Y did something and the actor acts (observer pattern).

Complex implementations rarely have a proper default implementation which fits all situations. You might get away with writing everything on a component but not usually. Otherwise you end up with components which are fine for one project but are not useful in others, think of Dark Souls targeting (locking the camera centered to target) where a TargetingComponent in another game would just tag one or even multiple targets in an entirely different manner. It’s possible… You’d just get a TargetingComponentV1,V2,V3 and so on :'). Either that or you use multiple more minimalistic components to target multiple targets and let the owning actor deal with the specifics such as providing data to widgets or dealing with the camera. That is normally my preference.

You are looking at this from the position of “all my stuff is an actor”.

In my project, I have dozens of classes that derive from UObject and I use interfaces heavily there. Components are not an option.

Actor Components are superior to Interfaces in many ways because you can have pure methods, events, etc. However I don’t think that is enough to say they should no longer be used or that their use somehow makes the code dirty.

For example, if you have completely diverse actors that happen to share a component, and an agnostic actor that wants to make calls to that component, how would you know it is an actor that has one of those components? Do you do multiple cast-to chains on the class or loop through every child from the root casting those components? What about an actor that does a capsule trace and finds a bunch of options? Do you add a trace channel for each one? A chain of cast-to’s? In this situation, interfaces would require a single cast-to and could augment using components rather than simply be replaced by them.

I am genuinely curious how you dynamically discover that an actor of unknown class type is in possession of a specific component that isn’t one of the methods I mentioned above.

Well yes but this is more of a flaw in the engine that there is no such thing as a UObjectComponent to serve as “plug in logic”. You could implement it yourself and use the same pattern. ActorComponents / Actors have more going behind the scenes though, mainly code related to networking and garbage collection. Actor based classes are a bit … tangled. I find it odd that the GameMode / State etc. is an Actor but at the same time I’m glad I can at least use components on them.
From the docs, which sounds irrelevant for classes like a GameMode (loaded once a level, no replication, no rendering, no transforms):

Actor is the base class for an Object that can be placed or spawned in a level. Actors may contain a collection of ActorComponents, which can be used to control how actors move, how they are rendered, etc. The other main function of an Actor is the replication of properties and function calls across the network during play. 

Since it’s not possible to swap a GameMode in the same level on runtime I use components to swap logic in and out, for example the rules to score in a match, if I have multiple “score modes” ready for one GameMode. Or perhaps multiple at the same time.

Many actor based classes currently in the engine can be removed and replaced by components honestly. Pawn / Character, Controller AIController etc. It’s ugly in its current state.

Since we know it is an actor (on all cases you mentioned) we can use:

USomeComponent* Component = SomeActor->FindComponentByClass<USomeComponent>();
if (IsValid(Component)) {
  Component->DoStuff();
}

This is basically free to do, not heavy on performance. If you’d instead call an interface on the actor the actor would then too have to get its component (assuming as in your example all actors have the component). A thing I don’t like about methods like FindComponentByClass is that we can hack into any actor with full access… but eh, engine design. It would be better if we could ask an actor for a component and if it could decide to publicly return it, but that’s another thing.

Yeah unfortunately that call (FindComponentByClass) is not exposed to Blueprints, so in a BP only project you would need interfaces.

I imagine that call literally iterates through every component on the actor and casts them one at a time. It is possible they keep a map using a hash of class to component under the Actor to make this faster than straight-up looping. Even still, replacing one (Hash-Based) cast for a cast to an interface is roughly the same. In fact, I think that one could easily find a use for an interface defining “What collection of components” a class has. In that case rather than saying “FindComponentByClass && FindComponentByClass…” you would just say “CastToInterface”.

I use both components and interfaces in my project and I think they both have value.

1 Like

GetComponentByClass is the BP version

2 Likes

I’d like to add a third method, since I don’t like the idea of “outside” actors directly talking to all and any components inside my actor and an interface is not always enough if you wish to talk to multiple actors which you might need to find first. In some cases I implement an event component, one which responds to events in the form of a string “mission_34_completed”, “martians_invaded_earth” on a central manager component within GameMode / GameInstance. Bonus is that you can read those events anywhere from actors to dialogs to mission logic wherever you need to know about world events. I based this on my previous project Road To The North.

Once you set up delegates between the event component and central manager, the actor / other component and the event component, there won’t be any casting at all for world events, only for smaller interactions which need to share data such as “pick up actor at line trace”. Technically… some of that can even be overcome if you’d always share JSON on an event and unpack it on the other end. Otherwise you’d get signature mismatches on data you want to send on different events.

Interesting read:

Implementing event-based communication between microservices (integration events) - .NET | Microsoft Learn

That is interesting. My single biggest complaint about interfaces is the lack of events.

I can see the value of componentizing Gamemode to replace some of the structure I have in place now. That would be pretty powerful.

Thank you! Not sure why they changed the name, but it is good to know it is there.

For various reasons. Blueprint (UFUNCTION) does not support overloading and not support templated methods etc. * shivers * C++ is full of blueprint garbage just to support it. Whatever BP started as, a prototyping / accessibility / low learning curve language, it’s severely outdated on all ends now. More like an outdated / dumbed down tool now used to attract non programmers to become non programmers. My unpopular opinion.

1 Like

I don’t think blueprints are bad - but I also use it to write almost exclusively procedural code. Procedural code in the graphs is very easy to follow, can be done extremely quickly, enables fast iteration, and very rarely runs into any issues.

Compared to the alternative of slapping Lua or Python and calling it a day, I prefer it. The moment I’d need to do something more complicated or structural, I can just switch over to C++.

1 Like

I don’t think so, for what it’s worth.
Blueprint is intended to be a “template” for an actor. You slap an instance of a blueprint down, and, voila, the blueprint builds the actor, with the appropriate mesh/materials/event handlers.

It turns out, some of the “art” bits need some code configuration. Or, alternatively, “code” can be viewed as “data,” as long as it’s simple enough. (Echoes of LISP here.)
Blueprint scripting fits that niche very well:
“What function gets called when this event is triggered?”
“What are all the conditions to actually open the hidden door?”
“Which pieces of data go into the widget on screen?”

If you try to build more game logic than that into Blueprints, yes, you’ll run into limitations of the model, but I think Blueprint scripting fills a very important niche, and I don’t think it’s “outdated” at all.

I’m looking forward to see where Verse is going. It may be that, for a complex and modular enough game, we’ll get the core in C++, the artist configuration (including behavior) in Blueprint, and the gooey middle of integration in Verse.