Handle Health/Combat via a component-based archiecture or inheritance?

Hi UE Community,

I recently started working with Unreal Engine 4 and am still in a phase where I am trying to figure out how things can or should be done in UE4. To get started, I am creating a top down space shooter (learning by doing :cool:).

I already created something similar in C# (a space invaders clone) and used a component-based architecture (yeah I know, thatā€™s overkill for such a simple game. It was about the learning experience :wink: ). I really like the benefit of being able to freely compose components on game entities to add behavior (like a health system/combat behavior, an RPG system with XP, etc.). I am at the point of implementing weapons and a health/shield system into my game and took a look at the Shooter Game example how itā€™s done there: They put everything into the Character class itself.

Would it be possible to create something like a custom Combat Component and add this component to my PlayerCharacter (and to enemies) to handle this stuff? I saw thereā€™s an ActorComponent for logic stuff, would this be the right one?
I saw thereā€™s a recent feature request hinting that all the possibilites arenā€™t quite there yet here: Feature suggestion: Complete Entity-Component-System for UE4. Though I have to admit I donā€™t know what the entity component-system in Unity looks like. I also saw a reddit post where someone asked a very similar thing, pointing out that Blueprintable Components seem to allow such a component-based architecture.

Or would you suggest sticking to the inheritance-based design the UE seems to advocate in the Shooter Game example?

Looking forward to hear some opinions and advice about the pros and cons of these approaches. Or if you think a fully component-based approach is even possible? :slight_smile:

Cheers
zaha

2 Likes

If you want to stick to the style of existing actor classes, you can do the health system in a component class and make the component itself a retrievable field. Take for example the character movement: logic in UCharacterMovementComponent, you can get a ACharacterā€™s reference to the component using ACharacter::GetCharacterMovement(). If you donā€™t want to put restrictions on what actor class has the health system, there are two ideas that I can think of:

  • Use AActor::GetComponentsByClass to find attached components by class. I wouldnā€™t use this one except for preprocessing (locating a component once and then storing the reference).
  • Make a ā€˜HasHealthSystemā€™ interface (UInterface) that any actor with a health system can implement, where the interface has a function to return the health system component (I prefer this idea over the previous)

The only pro/con I can think of for inheritance vs. component based is that:

  • With inheritance you never need to test whether the component exists or implements the interface, also slightly easier when you have systems that work together
  • With components you have plug-and-play functionality

Edit: There was no need to quote your first post. :wink:

1 Like

Thereā€™s already some ā€œdamageā€ type functionality in the base Actor classes I think. Iā€™m not a huge fan of inheritance for games, so Iā€™m using it, but iā€™m holding my nose while i do it :wink:

Seriously, all of this should come from composition. But UE is simply not ready for that quite yet. So i guess the question is, if you want to extend the component system or not? You COULD do these things with blueprint based components. They also have interfaces you can query for implementation etc.

Basically, the C++ code is a fair bit behind the blueprint code in that sense.

Thanks for your insight, guys!

After reconsidering it, I came to the conclusion I donā€™t need the absolute flexibility to add and remove components during run-time. That would be nice to have, but is not really mandatory for my game.
So I think I will go with something like your second approach, NisshokuZK, since I like it better as well. :slight_smile: I want to use components mainly for reusability and structural reasons, so I will be adding the components directly to my actors.

@zoombapup: I agree, composition is the way to go in modern complex software architectures. I will take a look into whatā€™s possible with Blueprints and components, thanks for the tip! I guess thatā€™s the best solution anyway, since Iā€™m working with a friend who is an artist and that allows him to try and contribute stuff for these parts as well. Do you know any resources on whatā€™s possible with components in Blueprints? Never heard of interfaces in Blueprints before.

My few cents, which I have leanred when I have been implementing (and still work on it for that matter), advanced effect system (buffs/debuffs/attributes).

tl;dr version is I would put health/damage handling/etc in Component. Componenets can be easily added/removed from actors, who said that all actors must have health and take damage ?

The long story is, my implementation is pretty complex, and you probabaly wonā€™t need such thing, for simpler system, but there are few things you can do:

  1. Create AttributeComponent. Make base class, which will handle Damage or attribute changes, orā€¦ you get the idea. How exactly you will implement it is up to you. Simple system will just call function like TakeDamage or ApplyDamageToTarget(), where you simply implement what happen, when damage have been dealt.
  2. Ok you have component. Now, how do we make sure that actor which is attacked have this component ? There are two ways. One you can simply call FindComponentByClass(), and check if there is component, and then call functions on it.
    Other way (proffered by me), is to creater Interface, which class must implement, if it have specific component (well, it doesnā€™t really need it, but itā€™s convenient). This interface contains exactly one function. GetYourComponentName(). You can add more functions, to interact with component systems if you wish.
    Donā€™t overdo this though. If get to the point where interface mirrors component functionality, you could just as well implement everything using interface.
    The rule of thumb is that, Interface should make interacting with component easier, and less painful for end users.

Then you can implement some static functions, to interact with this system trough blueprints.

Ok guys, so I created a UCombatSystem component that inherits from AActorComponent to handle health, shields, taking damage and dying. I took a look at the First Person sample to see how health and damage is handled there and some questions popped up:

  1. In the sample, the ShooterCharacter has a Health property of type float. Why do they use float and not int (uint16 or something like that)? Negative health isnā€™t really needed, or is it? Is it just because of the higher precision to have decimal damage values? Why does the GetMaxHealth method return an int32 then?
  2. Am I right in the assumption that this code retrieves the max health value by querying whatā€™s set in the archetype (default) object?

int32 UCombatSystem::GetMaxHealth() const
{
	return GetClass()->GetDefaultObject<UCombatSystem>()->Health;
}

So I can set the max health for all actors of the same kind in the defaults pane of the editor?

  1. As far as I can tell by the samples, itā€™s common practice to work with public members in the UE. For example, health is a public float variable of the ShooterCharacter. I feel kind of uneasy to publicly expose member variables like this (data encapsulation, I learned to make everything private unless I have a good reason not to). On the other hand, I see the advantages of simply being able to set the variable in the editor (like in the archetype for a default value on all actors of the same type). How do you work with this? Is there a way to achieve the same while having setters/getters and a private variable for something like this? Or am I being overzealous with best practices here?

Would be nice to hear some advice and about your experience. :slight_smile:

Thank you for your wisely advices, iniside.
I think I should consider using the Component Sytem cautiously.