What programming pattern am I thinking of here?

I use an ActorComponent to add logic to Actors, Pawns, Characters.
The ActorComponent can be configured from a struct property.
Methods on it like “CanIDo()” access this struct to determine to return true or false.

Depending on how big this component will get, this “setup struct” can grow up to infinity with the risk that only 1 half is used on the Actor and the other half on a Pawn. My idea to solve this is to define no default implementation for CanIDo and instead attempt to call CanIDo on the owning actor through an interface call.

Now we have the idea of an interface which has to be implemented on any owner which uses the ActorComponent, but resulting in duplicate code. So what pattern is optimal here?

Would I create an additional class just to implement CanIDo, to be dependency injected into the component? Or do I reimplement the interface on any actor I need it on? Or do I turn it into a library? Or do I just go with the initial setup struct and let it grow?

Imagine that CanIDo could depend on the output of an AI BehaviorTree, rendering the original setup struct on the component useless if I had implemented the logic there. It does seem like the components should be as abstract as possible?

  • Edit

To just give an example take a look at how ACharacter and CharacterMovementComponent are implemented, it faced the exact same situation.

APawn, ACharacter and variants should probably never have existed if all logic can be done through components.

The CharacterMovementComponent holds types of movement (walking / flying / swimming) which you might not use in your implementation of the actor and in turn the Actor might require logic not present on CharacterMovementComponent, such as custom gravity. So some form of injection would have made sense here.

well, you didn’t ask for a history of unreal lesson, but, it might help you contextualize :slight_smile:

In prior versions of Unreal (going back to at least early 4.0 betas, if not newer than that still), Pawn contained almost everything that is now in Character, and in CharacterMovementComponent. It’s been a several years long process splitting out all the bits into a more componentized structure, with minimal functionality implemented in lower level classes, and higher level in higher level classes and components.

When you are using multiple different components attached to a container, that’s basically the composition pattern, and descending base classes use the inheritance pattern.

Composition can be useful, combined with inheritance as well – if you have an actor that is likely to share all of these components, it’s useful to have accessors in the containing actor.

A reasonable way of implementing that is via an interface – in C++ interfaces, you can define a default implementation of your accessors, which could find the necessary components, and forward the call to the components, and then you can implement that interface in any of the actors that might have these components. If all or most of them do, though, then there’s not a lot of advantage to just building a base class with the accessors.

So, you have interface IActorCanDoThingsInterface which provides the CanDo*, which AActorThatCanDoThings implements, and contains the components that CanDo* accesses.

3 Likes

This is indeed what I think is the most optimal way to do it. It also decouples the component from other components. The more I am splitting up my projects into plugins the more I am removing dependencies and things that are not dynamic, like enumerators, when possible. I am exploring ways how I can use injection techniques and interfaces to decouple everything as much as possible.