Godot Version:
Godot 3.x
Note: I know this sounds a lot like MultiScript which I do NOT advocate, but let me know if this at all sounds like a good idea / doable.
Background & Issue Description:
Someone asked a common inheritance question: If you create a Character script, but want to have multiple types of nodes inherit it, how is this possible?
I gave them 2 options:
character.gd script and use is as a component, a child node, of whatever the root node of each scene is.The annoyance:
If you do things properly and go with option 2, you have to make sure that each of the associated root nodes have the same get_character method that returns a reference to the character.gd-owning node to create a common interface between them. You also have to bloat your code with calls to this method even when you would like to have imbued each of the root nodes with the content directly.
The abstract solution:
Find a way to designate that a script's associated properties, signals, constants, methods, and subclasses (hereby referred to as "Content") are also indirectly extended via another script.
For practical purposes, we would need to ensure minimal property and method collision: only the Content introduced by the script should be involved in the script indirect extension process. This means that 1) the extending script cannot derive from a node that deviates from the inheritance hierarchy of the original script and 2) only the Content that is NOT associated with the shared base types and script types can be extended into the original script.
Assuming we have a Node2D node as our root with the original.gd script attached and a single child Node node with extension.gd attached...SOMEHOW, we have defined that the extension.gd script will be mixed into original.gd
# base.gd
extends Node2D
var base_var = 0
# derived.gd
extends "base.gd"
var derived_var = 0
# original.gd
extends "res://derived.gd"
var original_var = 0
# extension.gd
extends Spatial # NOT permitted, results in an error (even if the node WERE a Spatial node). It would end up coming from a separate inheritance hierarchy.
extends "base.gd" # works because it is part of original.gd's inheritance hierarchy. Node and Node2D also would have worked.
var extended_var = 0 # works! original.gd should be able to access this method, so long as the child node with this script still exists and is associated with the parent node.
var base_var = 0 # triggers an error it is deriving base.gd and therefore has already declared this property
var derived_var = 0 # triggers an error because derived_var already exists in original.gd
var original_var = 0 # triggers an error because original_var already exists in original.gd
Steps to produce:
Now, my thought on HOW to produce this association is to create an array of NodePaths to represent component nodes. This would mean that only Node-deriving types would acquire this component functionality.
This ensures that data ownership and visibility are all preserved as much possible. We just cut out the middleman process of having to fetch the owned node or having to define the getter method.
So in an nutshell... you want to have a fallback on a user-defined list of child nodes if a member is not found on a given node? I doubt this works elsewhere than in GDScript. Also, the fallback may have a performance hit due to the hidden get_node().
@Zylann I don't see how this would have to be restricted to GDScript seeing as how it isn't tied directly to the way it implements the Script API.
No more performance hit than someone already would have dealt with when doing it the regular way. If it's a matter of storing references, then that might also be possible.
@willnationsdev in C# you would get no convenience since it would require you to use generic Get and Set, since this is a dynamic system by nature.
Storing references implies the node will stay where it is (nodes are not refcounted), making it secure would need to be notified when it changes which would be even more internal boilerplate.
It could also make the code a bit harder to understand since a function you would guess is on the object actually isn't, and it depends on its children. I would just do node.get_node("health").max_hp if I were to do a health component, it's as simple as having a category, with no list to maintain.
While I understand the initial motivation, I find the system a bit clunky. Even if you seem to want to avoid conflicts, it looks too much like multiple inheritance on a dynamic scale, I would not use it personally.
@Zylann Well, if it were a feature that only worked for GDScript anyway, then you could implement it even more simply by concatenating the components content onto the original script automatically every time a script on a component node was modified or every time the components array was edited. But that would make it a strange placement since it only works in Node and in GDScript. XD
In that sense, it might be more prudent to handle this as GDScript-specific enhancement where you keep the array in a GDScript object and you just think of it as GDScript Mixins rather than components.
Actually, I just thought of an interesting way to handle this.
What you could do instead is add support for C#-like type extensions. Where you define a static method on a script, have the first parameter be a reference to the type you are extending, and then automatically make that method accessible to all instances of the type. Perhaps have an EditorPlugin method that binds the method to a script resource? And that actually does sound like it would be more or less language-agnostic. Unless I'm missing something.
Someone asked a common inheritance question: If you create a Character script, but want to have multiple types of nodes inherit it, how is this possible?
In C# this is easy. You can create a class extending a node type, then extend that class.
Is this not possible in GDScript? I haven't tried. Something like Character.gd, and both Player.gd and Enemy.gd contain extends Character.
If this is solved, the rest of your post would be an XY problem from what I can tell.
@aaronfranke Wow this one was old. Forgot all about this.
Reading back over this, I think I worded this a bit poorly. The original problem was that someone wanted a more "trait"-like behavior in the engine where if they have 2 distinct inheritance hierarchies but they then wanted to have shared behavior between the two types, they could find a way to make a method call / property access, and it would end up getting picked up by some other child Node, but without any manual hooking up being necessary. That is, if a "Character" script had a "talk" method and the script was on a child node of the scene, then anything that instances the scene and calls "talk" on the root node would get automatically deferred to the "Character" sub-node because it implements the "talk" method. The parent wouldn't have to implement the method and then just call the child node's method manually to pass it along.
In practice / retrospect, I can see this being a very bad idea, at least with the proposed solution (what was I thinking? lol). If traits ever were to be a thing in Godot, it would be better for it to be a GDScript-specific feature, where you declare a script file and add a trait keyword to the top somewhere. That could then be registered as a script class automatically. In typical GDScript files, one could then use a use keyword (or something) to pair the trait with the GDScript file, effectively appending its content to the end of the script file during the parsing process. Maybe I should just completely close this Issue and make a new one for that idea (if a Trait proposal isn't already out there).
This idea truly and utterly sucks and is completely nonsensical. The OP proposal is effectively requesting to fundamentally modify the way the scripting API works and only so that it can save a bit of manual work, not to mention implementing a bad design as a result anyway.
Closing. May open up a separate proposal for a proper Trait system in the future.
@willnationsdev I would be very interested in seeing some discussion on how a trait system would get implemented. Been having some code duplication issues, but can't easily make them into traits, because there doesn't seem like there is much to support such a design.
For one I don't like having the export script variables getting buried in sub-scenes as they have to be expanded to be reached.
Just a lot of small things like that seem to pile up into a real chore.
@avencherus I mean, technically you can turn any GDScript into a trait by grabbing its source code, removing the first few lines (extends, tool, class name, etc.), appending the text to the 2nd GDScript file and then reloading it. That's more or less what I had in mind, except that GDScript as a language would find a way to do this magically using keywords and handling it all behind the scenes.
Most helpful comment
This idea truly and utterly sucks and is completely nonsensical. The OP proposal is effectively requesting to fundamentally modify the way the scripting API works and only so that it can save a bit of manual work, not to mention implementing a bad design as a result anyway.
Closing. May open up a separate proposal for a proper Trait system in the future.