Open me2beats opened 3 years ago
Child nodes could possibly be a relatively simple way, but adding behaviors as child nodes:
- could cause confusion and inconvenience, especially for containers.
- less efficient
- less flexible (since you can add only nodes to nodes)
- still needs the code that manages this, that is not handy
This is how the most of Godot exists, so I don't see this as valid criticism and shortcoming. Yes, composition is how you solve your problems. If you don't want additional nodes, you can always load and store an instance of a plain script on your object that would abstract a part of its logic and can be reusable.
This, in fact, is a more clear way to handle your problem rather than mixing several scripts into one. Sure, in some cases you'd have to write some boilerplate/proxy code, if you want to expose behaviors of your helper scripts, but it will be explicit and clear what the intention of the code is, how it functions and where to look for the implementation.
In addition to what @pycbouh you can also use inner classes to separate your script into tidy little parts.
@pycbouh writing boilerplate/proxy is the main problem for me, I need a simplier solution. This is also related to handling export vars, I don't want to create Inspector plugins for that.
@Zireael07 inner classes don't solve reusability problem
@me2beats: For export vars, there is a proposal about arranging them into subgroups.
And for reusability, child nodes are the way to go - your proposal is basically Unity style multiple scripts, just with some minor limitations slapped on top reminiscent of built-in scripts (which seem on the way out), and multiple scripts on single nodes as well as multiple inheritance were rejected by the team multiple times on grounds of creating special cases and being hard to maintain.
I need a simplier solution.
You may need to take a step back then and take a look at what you're proposing, because this is a very complicated solution for a problem that is solved perfectly well by composition. And not just in Godot. 😕
this is a very complicated solution for a problem that is solved perfectly well by composition.
perhaps we need to consider the most simple solution to this problem using composition or/and child nodes, because it seems we are talking about different things.
As for the complexity of the implementation - what could be the main difficulty? I do not see any obstacles. Due to the limitations for these extra scripts, imo this could be implemented much easier than traits (so we could see this in godot earlier than traits to make comlex things easier and not wait long)
Also afaik, Godot tried implementing multiple scripts before, but later refused it for some reason. This may have led to multiple inheritance problems.
The proposed solution does not have these problems and would be great for reusing at least independent behaviors.
I think the children-as-behaviors approach is the easiest, so let's take a look at it.
Here is the simplest example I could come up with.
In the scene we have a button node named MyButton
this is where I add behaviors (as child nodes).
Say when the delegator node is ready (it is MyButton
in this case), one behavior should print "hello" and another one should print "world"
So I create a Delegator
class, which should be a base class for MyButton
that has behaviors
property, that is Array of child nodes - behaviors.
And we override _ready() because we want to call all its behaviors ready()
methods when MyButton
is ready:
delegator.gd:
extends Node
class_name Delegator
var behaviors = []
func _ready():
for bhv in behaviors:
bhv.ready()
Also I create Behavior
class - base class/script for all behaviors.
I find this handy because we need to distinguish behavior nodes from other nodes and have an access to methods like _process()
to be able to delegate it as well (some time later) .
bhv.gd:
extends Node
class_name Behavior
func _enter_tree():
var behaviors:Array= get_parent().behaviors
behaviors.append(self)
Here, when a behavior enters the tree, it gets the parent node, that will be MyButton
in this case. Then it appends itself to the behaviors array.
And here are behaviors-scripts
hello.gd
extends Behavior
func ready():
print('hello')
world.gd
extends Behavior
func ready():
print('world')
As you may have noticed I am using ready()
instead of _ready()
.
I think there is no need to explain why.
And finally MyButton
script:
my_button.gd:
extends Delegator
func _ready():
print("do stuff")
[If there is no need to have "main" script (my_button.gd
), then delegate.gd
script could be set to MyButton directly, but I think it's a rare case]
And done.
When running the scene, we could see in the Output:
hello
world
do stuff
But there are some problems here.
Now I'm stuck on that my_button.gd
does not have an autocomplete for the Button
class because the script extends Delegator
that inherits from Node
.
I can't just change extends Node
to extends Button
in the Delegator
, since I don't know in advance what class is needed.
So it's something like: I'd like to script to extend Node but at the same I say "Hey autocomletion! treat this script as Button
.
Is this kinda interfaces system?
Now it seems like the only simple (though not very convenient) solution is to drop the "main" script when you have behavior scripts, and write all the logic in behavior scripts.
An alternative is to abandon the Delegate
class, but then the logic will become more complicated and flexibility will be lost [for example, you will not be able to delegate functions such as _process()
, etc.].
Another problem is that I can't seem to be able to get an instant call to enter_tree()
for behaviors in this way, it seems at least I need to wait for the next frame, this is due to the peculiarities of the node's entry into the tree, but maybe I need to study this problem in more detail.
Btw most likely I explain some of the generally accepted terms in a complex way, in that case, correct me pls.
And probably the most interesting thing for me is - could it be implemented easier?
At first I kind got confused of what of the purpose of this proposal, but upon my own research on my own Godot development framework, I found out that doing extra nodes indeed decrease performance and many cases it's very significant (> 20% of performance drops, compared to trying to write everything in single script). Plus, it's very difficult to construct clean code that only consists of single script per node/scene, without sacrificing performance for ease of maintainability.
I think the only reason that this proposal is required is for performance reasons, and GDNative/C# isn't feasible for all use cases (especially on HTML5).
but upon my own research on my own Godot development framework, I found out that doing extra nodes indeed decrease performance and many cases it's very significant (> 20% of performance drops, compared to trying to write everything in single script). Plus, it's very difficult to construct clean code that only consists of single script per node/scene, without sacrificing performance for ease of maintainability.
If you have a lot of nodes, it makes sense to use a single set of parent nodes that controls all the children (even if this involves creating a handful of nodes). This is expected to be faster than using a lot of child nodes, each with their own script.
Pls note: this is not for having multiple full-fledged scripts on a node/object like this https://github.com/godotengine/godot/issues/3205
Describe the project you are working on
Plugins
Describe the problem or limitation you are having in your project
I am creating a complex plugin that has many pieces of code which could (and should) be separated into independent classes and reused.
Multilevel inheritance only partially solves the problem and is not a flexible solution.
Example
For example, I have 3 independent script behaviors: x.gd: ```gdscript extends Object func _to_string(): print("x") ``` y.gd: ```gdscript extends Node func _ready(): print("y") ``` z.gd: ```gdscript extends BaseButton func _pressed(): print("z") ``` And for example I have a node `MyButton`, I want it to have all three behaviors `X`, `Y`, `Z` For this I have to create a script `xyz.gd` which inherits from `xy.gd`, which inherits from `x.gd` Thus, I created 2 new intermediate scripts `xy.gd` and `xyz.gd`, which I may not need at all. Also, I cannot easily dynamically enable/disable individual behaviors for a node, for example disable `Y` (for this I need to create new scripts like `xz.gd`, for all cases, which is definitely not handy. ---
Thus, I miss a convenient way to reuse scripts-behaviors.
Describe the feature / enhancement and how it helps to overcome the problem or limitation
Adding ability to set additional ("fake"?) scripts to nodes could solve the problem.
So, the problem above could be solved with additional scripts this way: In the inspector for the
MyNode
node, we could just specify three scripts:x.gd
,y.gd
,z.gd
and that's it!Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
The idea is a node/object may (or may not) still have a "main" script, but besides this, it may have several additional/extra ("fake"?) scripts.
I call them "fake" because they are not usual, full-fledged scripts:
These additional/extra scripts could work like this:
_init()
) should be sent to additional scripts of that node.Adding and removing additional scripts to a node could be done through the inspector, or using the code:
New methods (
Object
):void add_extra_script (Reference script, int script_index)
script_index
would set the order scripts are "triggered" (Useful for example if you have 2 extra scripts with_ready()
)void remove_extra_script(int script_index)
For removing a script by index Or alternatively:void remove_extra_script(Reference script)
for removing a script by reference.Reference script get_extra_script(script_idx)
int get_extra_script_count()
This proposal does not compete with the forthcoming (hopefully) trait system like https://github.com/godotengine/godot/issues/23101, they may well exist together.
If this enhancement will not be used often, can it be worked around with a few lines of script?
I don't see an easy way to wotk-round this. Child nodes could possibly be a relatively simple way, but adding behaviors as child nodes:
Is there a reason why this should be core and not an add-on in the asset library?
It cannot be an add-on, since it involves adding new functionality to the script system.