When to attach scripts on child nodes?

Godot Version

4.6

Question

Normally, I might have something like this:

CharacterBody3D (with script attached)
  - MeshInstance3D 
  - Area3D 
  - - CollisionShape3D

and I will have the script attached to the CharacterBody3D root scene to “orchestrate” everything.

But I see I could also attach scripts to the child nodes as well, but then:

  1. when would I want to do that?
  2. could somebody provide examples or designs when it is preferable to attach scripts to the child nodes?

It depends on your game and specific use case.
But generally, if you want to follow good practices, each node should do one thing and one thing really well. You shouldn’t have one “superclass” or in this case, supernode that does 10 things at once.

What I recommend is, see what your root node does. If your root node is also changing some things about the Area3D or other nodes under it, think about the following:
Could I add a very small and simple script to Area3D to do the work that the root CharacterBody3D was doing with the Area3D node? If so, you probably should do that.

Try to make it so each script file you have does only one (or just a very few things) that it absolutely needs to do, and nothing more. That way your project will stay modular, it’ll be easier to find bugs if you only need to look through a script file that is 15 lines long vs. 100 lines.

There is no good rule, a lot comes down to if it “feels right” or not.

For example, lets say you have a UI and two labels that display the screen size and FPS. Sure, you could add a script to each label and in one script get the screen size and update the first label and in the second script get the FPS and update the second label.

Or just have one UI script that gets both values and then updates both labels.

The latter solution would be simpler and easier to understand.

One way of thinking about it: Could I reuse this Node and its functionality somewhere else? If yes then you could add a script to it and basically turn it into a component that you can use in more than one place.

But if you will only ever use this one Node in one single place then adding a script probably doesn’t help much. But it could still be worthwhile if it helps you split long code into two pieces and make everything more readable.

Character rigs can get much more involved than just body+mesh+collider. For instance you can have an effect child or a weapon/item child that run their own animation/behavior scripts. A camera can be a child as well, running its own script. Etc…

There’s also a node-based state management pattern which maintains a bunch of child nodes, each running a script for a different state the character is currently in. In that case scripts run in a mutually exclusive manner.

I would recommend thinking about your nodes as objects. Ask yourself these questions:

  1. Is this node an object that can operate on its own, or does it require its parent to function?
  2. Can this node be re-used in other scenes?

Take a look at my comments in this thread, as I walk someone through better variable (and Node) naming. Having descriptive names helps you decide whether nodes are separate objects.

CharacterBody3D

Let’s take a look at a version of your example. This is from my Character 3D Plugin. (A version I have not uploaded yet.)

As you can see, I have kept the default name for the CollisionShape3D. It has no use outside of being attached to the KayKitPlayer3D (which inherits from a CharacterBody3D).

If you look at the Player Movement State Machine, you’ll notice it appears to have a script attached, but it is greyed out. This actually indicates that it is a custom Node added through the Add Node dialog. In this case, I’ve made a Node named StateMachine and another named State. I then created a bunch of custom versions of the State node that handle things that are pretty obvious from their names. They each do different things, and so they all have custom scripts.

You can see that two of these scripts are white. Those are scripts where I’ve extended the functionality of the built-in state instead of creating them from the Add Node dialog.

UI - Control Nodes

Here’s another example. A user interface.

Here we have volume controls for various Audio Buses. Each one has a Label with the audio bus name (in all caps so it can be localized - translated into other languages), an HSlider to control the volume level, and a Label to show the volume level as it is changed.

Each of the three parts has an attached script that is the same for all four buses. They each have an @export variable that indicates what bus they are for. They all act independently of the Audio node which is a custom Screen node. You’ll note it doesn’t have any custom code. It operates like a generic Screen, opening and closing as requested.

All the functionality is in the little scripts on the individual Controls - which each operate independently of the others. If a Bus doesn’t exist in the game, the entire row will disappear, because each control knows to hide itself. Both the slider and the percentage label will change their values by querying the Sound autoload to find out what they are set to. And whenever the slider is changed, it sends a signal tied to the Sound autoload that the percentage label is listening to - and it automatically updates itself when the value is changed.

While each object is part of a larger whole from the user’s point of view, adding them as little pieces allows me to keep the scripts small and generic for each function.

Other

For a longer discussion on how naming variables and nodes affects things, check out my replies is this post: Architectural Problems - #10 by dragonforge-dev