Benachrichtigungen in Godot

Jedes Objekt in Godot implementiert eine _notification -Methode. Ihr Zweck ist es, dem Objekt zu ermöglichen, auf eine Vielzahl von Callbacks auf Engine-Ebene zu reagieren, die sich darauf beziehen können. Wenn die Engine beispielsweise ein CanvasItem zum "Zeichnen" auffordert, ruft sie _notification (NOTIFICATION_DRAW) auf.

Einige dieser Benachrichtigungen, wie z.B. Zeichnen, sind nützlich um sie an Skripten zu überschreiben. Godot hat viele von ihnen mit speziellen Funktionen ausstattet:

  • _ready() : NOTIFICATION_READY

  • _enter_tree() : NOTIFICATION_ENTER_TREE

  • _exit_tree() : NOTIFICATION_EXIT_TREE

  • _process(delta) : NOTIFICATION_PROCESS

  • _physics_process(delta) : NOTIFICATION_PHYSICS_PROCESS

  • _input() : NOTIFICATION_INPUT

  • _unhandled_input() : NOTIFICATION_UNHANDLED_INPUT

  • _draw() : NOTIFICATION_DRAW

Was Benutzer möglicherweise nicht erkennen, ist, dass Benachrichtigungen nicht nur für Nodes sondern auch andere Typen existieren:

Und viele der Callbacks, die in Nodes existieren, haben keine dedizierten Methoden, sind aber dennoch sehr nützlich.

  • Node::NOTIFICATION_PARENTED: Ein Callback, der immer dann ausgelöst wird, wenn ein untergeordneter Node einem anderen Node hinzugefügt wird.

  • Node::NOTIFICATION_UNPARENTED: Ein Callback, der immer dann ausgelöst wird, wenn ein untergeordneter Node von einem anderen Node entfernt wird.

  • Popup::NOTIFICATION_POST_POPUP: Ein Callback, der ausgelöst wird, nachdem ein Popup-Node eine popup* -Methode abgeschlossen hat. Beachten Sie den Unterschied zu seinem Signal about_to_show, das vor seinem Erscheinen auslöst.

Auf all diese benutzerdefinierten Benachrichtigungen kann man über die universelle Methode _notification zugreifen.

Bemerkung

Methoden in der Dokumentation, die als "virtuell" gekennzeichnet sind, sollen auch von Skripten überschrieben werden.

Ein klassisches Beispiel ist die Methode _init in Objekt. Obwohl es kein NOTIFICATION_* Äquivalent hat, ruft die Engine die Methode trotzdem auf. Die meisten Sprachen (außer C#) verlassen sich darauf als Konstruktor.

In welcher Situation sollte man also jede dieser Benachrichtigungen oder virtuellen Funktionen verwenden?

_process, _physics_process und *_input

Verwenden Sie _process, wenn Sie eine Framerate-abhängige Deltazeit zwischen Frames benötigen. Wenn Code, der Objektdaten aktualisiert, so oft wie möglich aktualisiert werden muss, ist dies der richtige Ort. Wiederkehrende Logikprüfungen und Daten-Caching werden hier häufig ausgeführt, aber es kommt auf die Häufigkeit an, mit der die Auswertungen aktualisiert werden müssen. Wenn nicht jeder Frame ausgeführt werden muss, ist die Implementierung einer Timer-Yield-Timeout-Schleife eine weitere Option.

# Infinitely loop, but only execute whenever the Timer fires.
# Allows for recurring operations that don't trigger script logic
# every frame (or even every fixed frame).
while true:
    my_method()
    $Timer.start()
    yield($Timer, "timeout")

Verwenden Sie _physics_process, wenn Sie eine Framerate-unabhängige Deltazeit zwischen Frames benötigen. Wenn der Code im Laufe der Zeit konsistente Aktualisierungen benötigt, unabhängig davon, wie schnell oder langsam die Zeit voranschreitet, ist dies der richtige Ort. Hier sollten wiederkehrende kinematische und Objekttransformationsoperationen ausgeführt werden.

Während es möglich ist, die beste Leistung zu erzielen, sollte man vermeiden, während dieser Callbacks Eingabeprüfungen durchzuführen. _process und _physics_process werden bei jeder Gelegenheit ausgelöst (sie "ruhen" sich standardmäßig nicht aus). Im Gegensatz dazu werden * _input Callbacks nur in Frames ausgelöst, in denen die Engine die Eingabe tatsächlich erkannt hat.

Trotzdem kann man innerhalb der Eingabe-Callbacks nach Eingabeaktionen suchen. Wenn man die Deltazeit verwenden möchte, kann man sie nach Bedarf aus den zugehörigen Deltazeitmethoden abrufen.

# Called every frame, even when the engine detects no input.
func _process(delta):
    if Input.is_action_just_pressed("ui_select"):
        print(delta)

# Called during every input event.
func _unhandled_input(event):
    match event.get_class():
        "InputEventKey":
            if Input.is_action_just_pressed("ui_accept"):
                print(get_process_delta_time())
public class MyNode : Node
{

    // Called every frame, even when the engine detects no input.
    public void _Process(float delta)
    {
        if (Input.IsActionJustPressed("ui_select"))
            GD.Print(delta);
    }

    // Called during every input event. Equally true for _input().
    public void _UnhandledInput(InputEvent event)
    {
        switch (event)
        {
            case InputEventKey keyEvent:
                if (Input.IsActionJustPressed("ui_accept"))
                    GD.Print(GetProcessDeltaTime());
                break;
            default:
                break;
        }
    }

}

_init, Initialisierung und exportieren

Wenn das Skript seinen eigenen Node-Unterbaum ohne Szene initialisiert, sollte dieser Code hier ausgeführt werden. Hier sollten auch andere eigenschafts- oder Szenenbaum-unabhängige Initialisierungen ausgeführt werden. Dies wird vor _ready oder _enter_tree ausgelöst, aber nachdem ein Skript seine Eigenschaften erstellt und initialisiert hat.

Skripts verfügen über drei Arten von Eigenschaftszuweisungen, die während der Instanziierung auftreten können:

# "one" is an "initialized value". These DO NOT trigger the setter.
# If someone set the value as "two" from the Inspector, this would be an
# "exported value". These DO trigger the setter.
export(String) var test = "one" setget set_test

func _init():
    # "three" is an "init assignment value".
    # These DO NOT trigger the setter, but...
    test = "three"
    # These DO trigger the setter. Note the `self` prefix.
    self.test = "three"

func set_test(value):
    test = value
    print("Setting: ", test)
public class MyNode : Node
{
    private string _test = "one";

    // Changing the value from the inspector does trigger the setter in C#.
    [Export]
    public string Test
    {
        get { return _test; }
        set
        {
            _test = value;
            GD.Print("Setting: " + _test);
        }
    }

    public MyNode()
    {
        // Triggers the setter as well
        Test = "three";
    }
}

Beim Instanziieren einer Szene werden Eigenschaftswerte in der folgenden Reihenfolge eingerichtet:

  1. Initiale Wertzuweisung: Bei der Instanziierung wird entweder der Initialisierungswert oder der Init-Zuweisungswert zugewiesen. Init-Zuweisungen haben Vorrang vor Initialisierungswerten.

  2. Exportierte Wertzuweisung: Wenn Sie von einer Szene anstelle eines Skripts instanziieren, weist Godot den exportierten Wert zu, um den im Skript definierten initialen Wert zu ersetzen.

Infolgedessen wirkt sich das Instanziieren eines Skripts gegenüber einer Szene sowohl auf die Initialisierung als auch auf die Häufigkeit aus, mit der die Engine den Setter aufruft.

_ready, _enter_tree und NOTIFICATION_PARENTED

Wenn Sie eine Szene instanziieren, die mit der ersten ausgeführten Szene verbunden ist, instanziiert Godot Nodes im Baum (wobei _init -Aufrufe ausgeführt werden) und erstellt den Baum, der von der Wurzel nach unten verläuft. Dies führt dazu, dass _enter_tree -Aufrufe den Baum hinunter verschachteln. Sobald der Baum vollständig ist, rufen untergeordnete Nodes _ready auf. Ein Node ruft diese Methode auf, sobald alle untergeordneten Nodes ihre aufgerufen haben. Dies führt dann zu einer umgekehrten Kaskade, die zurück zur Wurzel des Baumes führt.

When instantiating a script or a standalone scene, nodes are not added to the SceneTree upon creation, so no _enter_tree callbacks trigger. Instead, only the _init call occurs. When the scene is added to the SceneTree, the _enter_tree and _ready calls occur.

Wenn ein Verhalten ausgelöst werden muss, das als übergeordnete Nodes eines anderen auftritt, unabhängig davon, ob es als Teil der Haupt- / aktiven Szene auftritt oder nicht, kann die PARENTED Benachrichtigung verwendet werden. Hier ist beispielsweise ein Snippet, das die Methode eines Nodes mit einem benutzerdefinierten Signal auf dem übergeordneten Node verbindet, ohne dass dies fehlschlägt. Nützlich für datenzentrierte Nodes, die zur Laufzeit erstellt werden können.

extends Node

var parent_cache

func connection_check():
    return parent.has_user_signal("interacted_with")

func _notification(what):
    match what:
        NOTIFICATION_PARENTED:
            parent_cache = get_parent()
            if connection_check():
                parent_cache.connect("interacted_with", self, "_on_parent_interacted_with")
        NOTIFICATION_UNPARENTED:
            if connection_check():
                parent_cache.disconnect("interacted_with", self, "_on_parent_interacted_with")

func _on_parent_interacted_with():
    print("I'm reacting to my parent's interaction!")
public class MyNode : Node
{
    public Node ParentCache = null;

    public void ConnectionCheck()
    {
        return ParentCache.HasUserSignal("InteractedWith");
    }

    public void _Notification(int what)
    {
        switch (what)
        {
            case NOTIFICATION_PARENTED:
                ParentCache = GetParent();
                if (ConnectionCheck())
                    ParentCache.Connect("InteractedWith", this, "OnParentInteractedWith");
                break;
            case NOTIFICATION_UNPARENTED:
                if (ConnectionCheck())
                    ParentCache.Disconnect("InteractedWith", this, "OnParentInteractedWith");
                break;
        }
    }

    public void OnParentInteractedWith()
    {
        GD.Print("I'm reacting to my parent's interaction!");
    }
}