2
\$\begingroup\$

Consider this situation, where a Player class inherits from a more general Actor class:

Actor.gd

class_name Actor extends KinematicBody2D

func _ready():
    print("I am an actor")
    _foo()
        
func _foo():
    print("I was thinking I was an actor")

Player.gd

class_name Player extends Actor


func _ready():
    print("I am a player")

func _foo():
    print("I was thinking I was a player")

When one instantiate a player in a scene, we have this output:

I am an actor
I was thinking I was a player
I am a player

How to know when a method will call its super class?

\$\endgroup\$
2
  • \$\begingroup\$ Could you try this with another function? Something other than _init and _ready? \$\endgroup\$ – Vaillancourt May 11 at 15:42
  • \$\begingroup\$ @Vaillancourt this is what I have tried with _foo() \$\endgroup\$ – pietrodito May 11 at 15:49
5
\$\begingroup\$

On the mechanism of Ready

The underlying mechanism for Godot calling _ready are notifications.

What a script observes is the following:

  • Godot calls _notification with NOTIFICATION_POST_ENTER_TREE (27).
  • Godot initializes any onready variables.
  • Godot calls _ready.
  • Godot calls _notification with NOTIFICATION_READY (13).
  • Godot emits the ready signal.

The mechanism is in Node::_propagate_ready (source):

  • First it will call _propagate_ready on the children nodes, recursively.
  • Then it calls notification with NOTIFICATION_POST_ENTER_TREE.
  • Then it calls notification with NOTIFICATION_READY
  • Finally it emits the ready signal.

Somewhere lost in there is when it calls _ready. We find that where Node handles the notification NOTIFICATION_READY (source).

When the Node gets NOTIFICATION_READY, it connects the handlers for _input, _unhandled_input, _unhandled_key_input, _process, and _physics_process (only if you declared them). Then it calls _ready. Will get back to that.

Thus, children nodes will get NOTIFICATION_READY first, and thus they will get their _ready called first.


Ready and derived classes

But all of that is without talking about inheritance. To be clear, I've been talking about children nodes in the scene tree, not derived classes.

The script is, well, an instance of the class Script. A Node has a Script. And there are different derived classes of Script for each language. We want GDScript.

This is important, because, for example, if it were CSharpScript it would use the inheritance mechanism of C#, and there you need to call base._Ready. Of which there are examples, as pointed out by Vaillancourt.


Let us look at the call of _ready again (source):

get_script_instance()->call_multilevel_reversed(SceneStringNames::get_singleton()->_ready, NULL, 0);

So, we get an instance of Script (of GDScript for our case), an we call call_multilevel_reversed on it.

This is how call_multilevel_reversed looks like (source):

void GDScriptInstance::_ml_call_reversed(GDScript *sptr, const StringName &p_method, const Variant **p_args, int p_argcount) {

    if (sptr->_base)
        _ml_call_reversed(sptr->_base, p_method, p_args, p_argcount);

    Variant::CallError ce;

    Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(p_method);
    if (E) {
        E->get()->call(this, p_args, p_argcount, ce);
    }
}

void GDScriptInstance::call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) {

    if (script.ptr()) {
        _ml_call_reversed(script.ptr(), p_method, p_args, p_argcount);
    }
}

As you can see, calling it will result in calling it on the base class first, recursively.

And there is the behavior you observe: Godot calls _ready on the base class, and then _ready on the derived class.


What about _foo? It is not special, Godot does not call it, it is a regular virtual call.


How to avoid the logic on the base _ready to run

The first answer is: Don't put it on ready.

You can do this:

Actor.gd

class_name Actor extends KinematicBody2D

func _ready():
    __ready()

func __ready():
    print("I am an actor")
    _foo()
        
func _foo():
    print("I was thinking I was an actor")

Player.gd

class_name Player extends Actor

func __ready():
    print("I am a player")

func _foo():
    print("I was thinking I was a player")

Since __ready is not one of the functions that Godot calls, it gets overriden as expected, and only the one on the derived class runs.

Drawback: you need to remember to use __ready.


This is arguably a cleaner way:

Actor.gd

class_name Actor extends KinematicBody2D

func _ready():
    if !get_script().resource_path.ends_with("Actor.gd"):
        return

    print("I am an actor")
    _foo()

func _foo():
    print("I was thinking I was an actor")

Player.gd

class_name Player extends Actor

func _ready():
    print("I am a player")

func _foo():
    print("I was thinking I was a player")

As you can see, this approach requires nothing special of the derived class.

Note: get_script().resource_path will not end in "Actor.gd" if it is a build-in script on a scene (It is a reasonable assumption that it isn't, since build-in script can't have class names). Also using get_script().resource_path will not work if the script is not loaded from a resource_path (e.g. it was created in runtime).


Thus, How to know if an overriden method calls its super method or not?

Well, given that we can call the method on the base class with the -let us call it- dot notation (e.g. .method()), checking if a method does it, would require source code analysis. However, if the question is how to know if an overriden method class its super method by default. Then the answer is that it does not. Instead there are some methods that Godot call differently.

\$\endgroup\$
0
2
\$\begingroup\$

I'm not an expert in gdscript/godot, but here is what I presume:

_ready being a "core" function acts similarly as a constructor. Typically (at least with a language like c++), when a new object is created, the most-parent constructor is called first, to make sure that the object is constructed when the child classes' constructor is called. The child not yet being constructed, if the function foo is called in the parent's constructor, it's the parent class's foo that will be called.

Although we have similarities here, it's not exactly the same.

_ready being a utility function, it will be called in order of the most parent's to the most child by the framework, making sure that what's "initialized" in the parents is initialized for the children--just like a constructor. However, unlike it would be the case in a constructor, all the objects having already been created, the "most overridden" function foo will hide all those that have been defined its ancestors.

Taking a look at the source code, there are multiple occurrences where a "child" _Ready() will call it's base._Ready() as the first thing it does, so it appears this is consistent with the theory.


How to know if an overriden method calls its super method or not?

It's probably safe to say that the "top ancestor" will have it's _ready function called first, down to the "most overridden" _ready function. This behaviour will likely be the same for _init, although I have not found anything relevant about it.

For the other cases, the normal "hiding" will be done, and you'll call the the "most overridden" foo function.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.