This is more of a “best practices” question:
I have a node that represents a villager. The node itself is a CharacterBody2D, so that it can use the move_and_slide method. The villager will have to have 2 additional components: the job handling logic and the character sheet-style object (handling HP and other parameters). As of now the job handling is done via a JobHandler node, which can accept various types of Task scripts, depending on the role of the villager.
My question is: what is the better approach here? Should I add the character sheet script as a separate node or read it in as a variable in the CharacterBody2D node? Should I drop the JobHandler node and handle job types as separate nodes?
Each project is different. You know best what your requirements are. I could help you better if you share some more information.
What do you mean by accepting various types of task scripts? Are these instances of a Task class? Are they separate task classes that you construct instances of? Are the tasks dynamically assigned or do you manually put them there in the editor? If your JobHandler has 200 lines or less of code, you might merge it with the villager script. Otherwise, if you need complex task handling, the jobhandler is fine to use.
What do you mean by character sheet-style object? Is this a resource? Does it use exported variables that you manually set in the editor? Are hp and other parameters randomly assigned during gameplay? I’d probably read it in as a variable in the CharacterBody2D unless you want the character sheet to manage it’s own variables and respond to signals to update itself. The whole point of the modular component-based structuring is that they can be independent of the parent node. When it comes to HP and other parameters though I can see it getting very interconnected with the characterbody2d node so you’d probably be better off reading it into a variable.
OK, here’s some more details:
I created a DestructibleObject class_name, which has the common functions regarding handling HP - receiving damage, healing, “on hit” effects and so on. This class is later inherited by a number of objects in the game, such as buildings, resource nodes and also characters, like villagers or enemies. This is what I call the “character sheet”, as for the living entitirs it will also have additional parameters, which will be somewhat randomly generated during creation of a new villager entity.
The DestructibleObject itself does not extend Node2D or a Node itself (maybe it should?), and so for buildings I just load it as a variable called “building_script”, and for villagers it will be “villager_script”.
As to the jobs, this is a bit more complex, as villagers’ roles may change during the course of the game. So what I did is to have the JobHandler node, which manages not only the jobs assigned to the villager, but also “self-preservation” jobs like fleeing from danger or eating/sleeping. Aside from that I have Scripts for various job types (e.g. worker, farmer, hauler, builder etc.), which can be loaded into the JobHandler (as a variable) and the JobHandler’s task is to manage the tasks that a given villager is performing. So in this way I semi-manually can assign a job to, not in editor but in-game (e.g. assigning the worker to a different building or work station).
My main issue is that if I were to use a separate node for e.g. each job type, I would have to check for node names, have a lot of signals passing when a node is added/removed from the tree etc. If I do it as a variable, I can just switch it out and since all the job types have the same inherited methods (all inherited from some abstract Job class), the integration would seem more seamless to me.
Another question is, in my DestructibleObject class would it be better for it to extend Node2D or not?
You say you created the DestructibleObject class and it’s inherited by other nodes, but then you say you’re loading it as a variable in the building and villager instances. So it’s not really inheritance but rather composition. In this case, it is not required to have DestructibleObject extend node or node2d, unless you need properties like position in the DestructibleObject script. I don’t see a problem with this but if you’re going for composition it might be a good idea to review composition best practices and see if your approach is in line with that.
As for the JobHandler, it seems like you are describing a state machine, where the villager can assume one state based on a selection of states (tasks in your case). If this is what you want to do, then I’d stick to the state machine principles and not separate the nodes for each job type.