How do I minimise boilerplate code and hard dependencies when actor components need to interact with each other?

The problem

Short TLDR: I am wondering what’s the simplest, cleanest and least boilerplate-heavy way to have actor components depend on other actor components and interact with them.

Longer version:
I’m posting this here because I would like to know best practices or idiomatic approaches to the sort of problem I’m repeatedly having.

Say you have an actor with various components handling logic, but a lot of those components need to interact with each other in various ways. Here are a few baseline things that made me want to split logic in components and keep characters as empty as possible:

  • The vast majority of my gameplay logic should be shared between almost any character, but I don’t necessarily want all of them to share the actual character logic. I want to keep the ability to create new character blueprints that don’t inherit from some big master class that might be too inflexible
  • I want to avoid code duplication in different character types as much as possible, because when this happens I have to duplicate any changes I make to multiple character blueprints.
  • Composition over inheritance: Keeping the character itself as lean as possible should make it easy to create new types of characters that combine components in specific ways.

Possibly relevant detail: Most of my game engine experience is with engines based on entities that all have transforms, components and children entities, like Unity. I’m still often a bit confused by how Unreal actually wants to be used, hence this question.

How do we get the dependencies

The main problem here is simply that within components themselves, there’s no easy way to interact with the other components. Here are a few options:

  • Get the owner, cast to your character type, get the component: pretty bad, creates a hard reference from component to character, probably a circular dependency, not very reusable
  • Give a blueprint interface to the owner with functions to return other components: Better, no hard dependency on the character, but you have hard dependency on the other components. Cons: Every implementation of character needs boilerplate code to implement interface functions to return the components they use. Components still have hard references to each other, which can create circular dependencies.
  • Same BPI idea, but instead of returning components they have functions for specific things they can do, which use components behind the scenes. Pros: No dependencies, basically event-driven code. Cons: Tedious: Characters require lots of boilerplate code which often just wraps a function in a component, which sort of breaks the benefit of splitting logic into components. This is how most of my project is done so far, and by character just has way too many BPI functions that are basically just returning what another component returns. Pros: The functions in the character can be tweaked on a per-character basis, which is sometimes useful.
  • What I’ve started doing lately: A blueprint interface for each component type, then we get components by interface from the owner when we need to interact with them. Pros: No dependencies, event-driven code, components manage their own logic without having to know how the parent works, and the characters don’t need to know anything about it. This is the one that requires the least code on the character level. Cons: Doing this requires tedious calls, casts, and checking that the component array isn’t empty. It’s a lot of boilerplate whenever a component wants to use another. Also, creating BPIs for every component feels redundant.

Generally, in all the cleaner approaches using interfaces or functions to return components, there is still a common problem of boilerplate code:

The component needs to interact with various of the other components, which means on begin play finding them and setting them to variables. This is very tedious boilerplate code.

I wish there was some kind of clean way of specifying component dependencies in a component and then being able to automatically send them messages, maybe via interface, but without having to go through the whole process of manually finding them first, or having the owner actor provide functions to return them.

yeah im going through the same, i think the best way is to put the interface on the owner not the component that way if the component doesnt exist it can still return or fail. this also lets you override for example if i want to get health on my character i can call from a stats component but if i want to get health on a crate i can just return the variable directly.

otherwise you could initialize the component on the actor which passes through required dependencies. i like this option as you also know exactly when the component is loaded.

another way is to have components call events on the owner using dispatchers but this still requires more steps.

I am curious how others do it and i really wish interfaces could return delegates

Yeah, except this brings the tediousness problem of implementing interfaces on the actors.

I’ve started making a blueprint function library with functions to return either the components, or the interface to the component. It gets the owner, finds the component, returns it, and also returns a boolean on whether one was actually found.

It has one reusable “get component by BPI” where you can pass the BPI interface class (turns out it’s possible):

Then more functions using this one can be created for each type of component BPI you want to provide easy access to:

Then all you need to do is call the functions library in other places, passing the component you’re in. For example, here it’s used in my anim notifiers, because I realised anim notifies had hard dependencies on the components they interacted with, which was causing animation montages to have hard dependencies on components, and you can see how this would create a very circular dependency tree. Now they only depend on the interface, and the blueprint function library.

I’ve also made a separate blueprint function library for getting the components themselves, not the interfaces. Ideally I want to stop doing that as much as possible, but having an interface for every single component function that might need to be used is tedious, so sometimes it’s convenient:

Then with this library, you can get components from the owner with a bit less boilerplate:

I still find it tedious to have to do this on begin play and create variables for it, and wish the engine had some kind of dependency system for component interaction, but this is the least boilerplate-heavy way I’ve found so far.

Then it’s up to you to decide whether you want to use interfaces to have less direct dependencies, or just helpers to get the components themselves. Either way, doing this allows it to work on any actor, and the actors don’t need to implement any interfaces.

What’s the issue with some variant of the first method?

From each component that needs to interact with the others, you can get your owning actor’s component-of-specific-class (post-initialization, or at another point) and if it exists, use the reference. I assume this is what you mean by your first point, but you don’t need to cast to any character type, since these functions exist in AActor.

i agree its a pain, but is more modular and more efficient, get component by class is ok but it still has to iterate over all components and you have to deal with a fail condition

i went down the BPI route too but scrapped it because you cant bind to events, i just create an component base now and pass that through. it is a hard ref, but its lightweight and you can alter anything in its children anyway

but i too am looking for better ways

Sort of, I meant casting to the specific class so that we can access the component variable, as opposed to having to call “get component by class”. It’s still tedious to do though, and part of my problem on dependencies is that getting component classes directly causes dependencies on components.

I’m also worried of the performance cost of having so much code go through interface messages, and having to search for components so often.

Like mentioned in the above post, I ended up with animation montages that have dependencies on half my components because their anim notifiers call functions on the components.

The engine generally seems to handle those kinds of dependencies, except when it doesn’t. I have data assets breaking and losing all their data when closing/reopening the editor due to obscure UE5 bugs that don’t seem to have any solution but seem to be caused by circular dependencies, so I was trying to organise my code in a way that minimises hard references.

The main goals are just:

  • Avoid hard dependencies as much as possible to limit the risk of circular deps
  • Have as little boilerplate code needed as possible for something to interact with a component on an actor

The lack of ability to use events is really a problem for me too… I end up with a lot of code checking values on tick to detect changes, not ideal. Blueprints don’t seem to allow a lot of async/delegate/event programming, outside of very specific circumstances, sadly.

There is one possible thing that might help with this which is macros. Macros, unlike function libraries, get inserted in the event graph. This means they can have execution pins and I think should allow using delegates. I haven’t looked into what would be a good way to use macros to solve this problem yet, but something might be doable to make things easier.

Of course you still have the limitation that none of this can be used in functions, so it would only work in event graphs

GetComponentsByInterface sounds pretty good to me, maybe all you need is a wrapper/macro to auto-return the first element, to avoid dealing with the array, kind of like GetActorOfClass was added after GetAllActorsOfClass.

When working with interfaces in BP, you don’t need to cast. You can store UObject (or derived) type variables and call interface methods on them directly.

Ah yeah, that might make things a bit easier, then I could use my GetComponentByBPI function for everything instead of needing to make one per BPI type to return the right interface type…