What does a component-based workflow look like?

Hi all.

I want to organize my gameplay logic into blueprint components in order to stay organized and to not repeat code. (I’m using blueprints only.)

I have a fairly large function that is both in my player character’s BP graph, and in an enemy’s BP graph (I just copy-pasted the nodes… mostly). So it seems evident that I should just turn it into a component and attach it to both of these actors.

Except, it’s not EXACTLY the same, there are some parts of it that work differently in the enemy’s case compared to the player’s. So what would be the best approach here?

  • Obviously I shouldn’t make two slightly different versions of the component, because then we’re back to duplicating code.
  • Should I somehow detect from the component which type of actor it’s attached to, and make branching functionality based on that? Doesn’t seem like good practice, since I don’t know in advance what kind of actors might end up using the component, maybe later 20 different actors will have it, and I would have to expand the component’s graph with more and more branching.
  • Should I separate out these small differences into further components? Also doesn’t seem like the best idea, since some of these differences are just one node differences, so having another component just for that sounds silly. Also, it would mean I might have to make dozens of these sub-components for more of these slightly different pieces of functionality later.

So basically, how do you avoid copy-pasting (mostly the same but slightly different) stuff across different actors?
For those of you who use a component-based workflow: what are some best practices?

1 Like

It depends on what the component does.

Sometimes it’s better to extract the functionality as a manager to a separate actor and reference that through the characters.

Too many components hurts performance so it’s best to not go too crazy with them as a workflow.

If you do use them then stick to Actor Components as they don’t require extra information like transforms.

You can us get owner to check who is “wearing” the component and do specific actions based on this. You could also use a variable of type Enum set set specific types of behaviors.

2 Likes

A component is used to give common behavior to different actors

you can parameterize that behavior using instance editable variables.

For example, in the component, you can make a boolean called “WillJump” and set it to instance editable. Then on a branch, if WillJump is true, the enemy will do jumping behavior.

Then if you have multiple different blueprint classes which implement this component, you can set the WillJump variable how you want for each one.

You can see this in action by looking at the character movement component which is attached to the character class. It has a bunch of different variables that you can set on every different class which uses the component.

Whether you separate a chunk of logic to a different component or not is a question of maintainability, organization, and optimization. A generic answer is not easy to give.
My best advice is to make as few separations as possible and only make a separation if there becomes a big problem which motivates you to do so.
Because the more you separate your code, the slower it becomes to work with.

This assumes you are working alone. If you are in a team, a lot more separation becomes mandatory, especially with blueprints.

But a good way to start would be just put all of the behavior into a component, and then if some chunk of logic is growing so large that you feel it will be easier to maintain by splitting it into its own component, then you can cut that out and transplant it.

1 Like

If you could share a fragment of what you consider slight difference.

Hard to say. Maybe checking who controls the owner is enough or if two dedicated classes is the way to go.

1 Like

Thank you all for the replies! So it’s basically “depends on the situation”, but what I’m getting is that branching within the component based on what actor it’s attached to is a valid thing to do (and seems enough for my situation).

@BIGTIMEMASTER and @3dRaven
Using enums for the branching is a good tip, more elegant than hard-coding the actor type checking, thank you!

@3dRaven Now that’s interesting, I was under the assumption that only scene components have an impact on performance. When you say “don’t go crazy with them”, do you mean maybe a couple dozen or a couple hundred components / actor? (If we’re talking about simple actor components only, not scene components.)

And that makes me question my base idea too. Is this workflow totally unusual in Unreal? Basically having no or very little functionality on the actors themselves, and putting everything into reusable components?

A couple of componets is ok but if you need a 100 then perhaps you need to find a better design pattern at that point.

Each component will have some slight overhad to access it / use it. Take into account that this will stack up with each instance if the actor in your scene because they are all evaluated separately.

Turn off tick on components it they don’t need it.

Also remember you can use inheritance on components. So you can have 2 variants of a component with common code. One can hold base information while the second can extebd that by properties and functions.

Ok, thanks for the input. Good point about inheritance too.

So this is maybe not directly related to the original question, but other than using components, what other options are there to avoid having to duplicate nodes across actors?
My brain is a bit fried right now, but I can only think of an inheritance-based setup with a bunch of parent-classes that each hold one functionality, but I would rather not go down that route.

I’m spawning hundreds of actors, so every bit of performance loss is important.

Like I mentioned earlier you can have an over-arching manager in the form of a blueprint library or module with static functions (requires c++). You can also have an actor passed by ref but it’s not as easy to use.

It can be detached from any actor and shared among them (like Game Instance, Game Mode). You could then call it’s functions from anywhere in the game passing in inputs for calculations or reference actors if they need to have their variables modified and returned.