Search code examples
godotgodot4

Is there a way to create a "Prototype function" framework in Godot?


For a game I'm working on, I wanted to make a ring of cannons, and attach to these cannons a node that controls their logic (e.g, what cannons shoot when, what they fire, etc). I want this node to be extremely flexible, so that all I have to do is write a new function for the logic in some sub node, and then when the subnode is ready it will emit a signal, passing a list of all of the functions created up to the central node, which will then decide which function to use based on properties, user input etc.

In order to ensure correctness when creating each function, I was hoping to create some sort of "prototype", which basically dictates that all functions in a node needs to follow a specific set of rules, e.g, they must all take an arg of a list of Cannon objects, and an index, and all must return a list of Cannon objects. I was wondering if there is anything within the Godot framework that would allow for this. Effectively an interface, but for multiple functions within a node.

An interface would also allow me to more easily call references to the function in the parent node, since I don't have to worry about ensuring the correctness of my calls.

The closest thing I have found to this would be using a class with nodes, but that requires creating a new node for every function, which sounds messier than just having one central script that contains them all.


Solution

  • This is less about Godot and more about the languages.

    First of all, if you use C# you would be able to define interfaces. And the same would be true for any third party languages with that or a similar feature.

    However, GDScript does not have interfaces (or any other kind of contracts), instead it relies largely on duck typing. There are proposal that might address this in the future (Add a Trait system for GDScript), there are also third party solutions that ease the duck typing.

    With duck typing going into the trouble of checking the expected parameter types can be troublesome. See How can I check if call, callv failed.


    From here on, I'm going to assume you are using GDScript.

    Now, even thought GDScript does not have interfaces, it has inheritance. And you will find stronger guarantees if you use inheritance.

    Every GDScript script is a class. And you can give it a name with class_name. So you can define a base class with the methods you want:

    class_name MyBaseClass
    extends Node
    
    func method_name(arg:bool) -> int:
        return 0
    

    And then create a derived class where you can override said methods (you don't have to):

    class_name MyDerivedClass
    extends MyBaseClass
    
    func method_name(something:bool) -> int:
        return 1
    

    And Godot will tell you if the method signature does not match, statically.

    However, if you wrote the method name wrong, Godot will consider it a different method. And since GDScript does not have abstract methods (Add support for abstract classes in GDScript), you are not forced to implement them all... As a result, the typo would fail mostly silently...

    If the method overrides correctly, and there are no syntax errors, you should see a blue arrow icon on the border of the editor, next to the line number: Blue arrow Icon. Clicking it, should take you to the overridden method in the base class.


    An interface would also allow me to more easily call references to the function in the parent node, since I don't have to worry about ensuring the correctness of my calls.

    A class should be sufficient (which can be a class defined with class_name or not):

    var my_parent:ClassOfTheParent = get_parent()
    

    See also What does the "as" keyword do in GDscript? How is it different from just declaring a variable?.


    The closest thing I have found to this would be using a class with nodes, but that requires creating a new node for every function, which sounds messier than just having one central script that contains them all.

    I don't think there is a solution in GDScript that would allow you to have them all in a single script, and checked statically (i.e. without runtime checks).