#Composition and referencing Component within the parent ?

1 messages · Page 1 of 1 (latest)

distant adder
#

Hi everyone.
First post here, let me know if this is consistent with the forum rules and guidelines.

I've been refactoring a bit the code to include more "composite" approach, as demonstrated on a youtube video from Bitlytic
I like the "Lego" approach where I can simply attach behavior to entities by drag and droppin' prebuild components (scenes) to them, rather than relying solely on inheritance.

For my Example, I have a Character Class. and this node has a child scene included "HealthComponent".
HealthComponent as a built in script & class which manage Health of the component. It's logic is supposedly limited to managing Health, with methodes like damage(hp: int), heal(hp: int); etc.

Not every Character has a Health Component. Health component is supposed to be optional.

my problem is whenever a Character could take damages. I want to be able to call health.damage(3) directly from the Character.

I'm unsure of the best approach here to reference the component on the parent level.

@onready var health: HealthComponent =$HealthComponent
won't work because Character may not have a HealthComponent.

should I use, within Character, something like
@onready var health: HealthComponent = get_node_or_null("HealthComponent") as HealthComponent
or
@export var health = null
and assign this within the UI for each Character (or more likely anything that derivates from Character ?)

or something else ?

is there a scenario where I could call health.damage() from my Character without having to test if health is empty ?
I understand there are possibly multiple approaches with various pros and cons. Curious about your opinion and possibly best practices.

Thanks for your help !

gray ore
#

When I'm working with components like this, I usually have an @onready reference to the component. Lets call the variable hp_component for this example. The node that owns the hp_component (your Character) has a method called damage_hp(amount :int) which calls hp_component.damage(amount). With this method, you can test with if character.has_method("damage_hp") to ensure the method exists before attempting to call it.

I typically rely on physics layers to determine what can collide to even attempt to call damage methods, though. You can have a HitBox layer and a HurtBox layer set up so that your HitBox scenes can only see HurtBox scenes, which means they will only ever see objects which definitely have a HurtBox. HitBoxes and HurtBoxes can be separate components or part of your HP Component scene. Making at least the HurtBox part of the HP Component scene can ensure that if an entity has HP, it can take damage.

distant adder
#

hi @gray ore, thx for the detailed answer.
I made an attempt with your approach, it works fine for me with minor tweaks ! 🙏

so, class Character has @onready var health_component: HealthComponent = $HealthComponent

First lesson I learnt : It works even if there is not such component node. In that case, health_component == null, which is great. (I thought it would break the class)

I added a method damage to the class Character. But instead character.has_method("damage"), I had to test if health_component existed.
the method looks like this :
func damage(amount: int) -> void:
if health_component:
health_component.damage(amount)

Basically means if Character has health, then go ahead and damage it.
testing the has_method on the health_component would throw an error when health_component is null.
was this concept you offered in your solution ?

Initially, I was reluctant to add "damage" logic (= method damage(int)) to the Character class, since I wanted it to be strictly the responsibility of the component. But in the end it kind of makes sense. Character needs somehow to explicitely support components it's compatible with. It already does since it has the declaration of the variable health_component. So if character has health, damage it health on character level is ok I think. Same logic for every component that's supported by Character I guess.

Besides that, yes I'll have hurtbox and hitbox components as well, even though I'm not entirely relying on physics / entered signals for combat-collision detection, but that's another story.

gray ore
#

testing the has_method on the health_component would throw an error when health_component is null.
I typically run that test on the Character, rather than the Health Component itself. I set up every entity that uses the Health Component to have the same names for methods that call down into their Health Component. I actually have a file that just contains snippets so I can copy/paste the methods I need for any given component.

Glad it worked out for you!

distant adder
#

Ah yes, i think I understand now. Any of your entities that needs health has it, so you don't need the nullcheck when calling the health component. you just need to check that the entity has the method, if yes it will call the component's one👌