Skripten (Fortsetzung)

Prozessverarbeitung

Mehrere Aktionen in Godot werden durch Rückrufe (Callbacks) oder virtuelle Funktionen ausgelöst, sodass kein Code geschrieben werden muss, der ständig ausgeführt wird.

Es ist jedoch immer noch üblich, dass für jeden Frame ein Skript verarbeitet werden muss. Es gibt zwei Arten der Verarbeitung: Leerlaufverarbeitung und Physikverarbeitung.

Die Leerlaufverarbeitung wird aktiviert, wenn die Methode Node._process() in einem Skript gefunden wird. Sie kann mit der Funktion Node.set_process() ein- und ausgeschaltet werden.

Diese Methode wird jedesmal aufgerufen, wenn ein Bild gezeichnet wurde:

func _process(delta):
    # Do something...
    pass
public override void _Process(float delta)
{
    // Do something...
}

Es ist wichtig im Hinterkopf zu behalten, dass die Häufigkeit in der die _process() Methode aufgerufen wird, davon abhängt, mit wie vielen Bildern pro Sekunde (FPS) die Anwendung läuft. Diese Rate kann auf verschiedenen Geräten oder auch über Zeit variieren.

Zur Berücksichtigung dieser Variabilität, enthält der delta Parameter die vergangene Zeit in Sekunden als Fließkommazahl (float) seit dem letzten Aufruf von _process().

Dieser Parameter kann verwendet werden, um sicherzustellen, dass Dinge unabhängig von den FPS des Spiels immer dieselbe Zeit in Anspruch nehmen.

Zum Beispiel wird die Bewegung oft mit einem Zeit-Delta multipliziert, um die Bewegungsgeschwindigkeit sowohl konstant als auch unabhängig von der Bildrate zu halten.

Die Physikverarbeitung mit _physics_process() ist ähnlich, sollte jedoch für Prozesse verwendet werden, die vor jedem physikalischen Schritt ausgeführt werden müssen, z.B. zum Steuern eines Charakters. Sie läuft immer vor einem physikalischen Schritt ab und wird in festen Zeitabständen aufgerufen: standardmäßig 60-mal pro Sekunde. Sie können das Intervall in den Projekteinstellungen unter Physik -> Allgemein -> Physik Fps ändern.

Die Funktion _process() ist jedoch nicht mit der Physik synchronisiert. Die Bildrate ist nicht konstant und hängt von der Hardware- und Spieloptimierung ab. Die Ausführung erfolgt nach dem physikalischen Schritt bei Single-Threaded-Spielen.

Eine einfache Möglichkeit die _process()-Funktion bei der Arbeit zu sehen besteht darin, eine Szene mit einem einzelnen Label-Node zu erstellen. Sie können dies mit dem folgendem Skript erledigen:

extends Label

var accum = 0

func _process(delta):
    accum += delta
    text = str(accum) # 'text' is a built-in label property.
public class CustomLabel : Label
{
    private float _accum;

    public override void _Process(float delta)
    {
        _accum += delta;
        Text = _accum.ToString(); // 'Text' is a built-in label property.
    }
}

Es zeigt einen Zähler, der die Frameanzahl erhöht.

Gruppen

Gruppen in Godot funktionieren wie Tags, auf die Sie in anderen Softwares gestoßen sein könnten. Ein Node kann beliebig vielen Gruppen hinzugefügt werden. Dies ist eine nützliche Funktion zum Organisieren großer Szenen. Es gibt zwei Möglichkeiten, Nodes zu Gruppen hinzuzufügen. Die erste ist über die Benutzeroberfläche über den Gruppenknopf unter dem Node-Panel:

../../_images/groups_in_nodes.png

Die zweite Möglichkeit erfolgt via Code. Das folgende Script fügt die aktuelle Node zur Gruppe "enemies" wenn sie im Scenetree erstellt wurden.

func _ready():
    add_to_group("enemies")
public override void _Ready()
{
    base._Ready();

    AddToGroup("enemies");
}

Wird der Spieler dabei entdeckt, wie er sich in eine Geheimbasis schleicht, können alle Gegner über einen Alarmton informiert werden indem SceneTree.call_group() benutzt wird:

func _on_discovered(): # This is a purely illustrative function.
    get_tree().call_group("enemies", "player_was_discovered")
public void _OnDiscovered() // This is a purely illustrative function.
{
    GetTree().CallGroup("enemies", "player_was_discovered");
}

Der Code oben ruft die Funktion player_was_discovered bei jedem Mitglied der Gruppe enemies auf.

Es ist auch möglich, die vollständige Liste der Feinde-Nodes durch Aufrufen von SceneTree.get_nodes_in_group() zu erhalten:

var enemies = get_tree().get_nodes_in_group("enemies")
var enemies = GetTree().GetNodesInGroup("enemies");

Die SceneTree -Klasse stellt viele nützliche Methoden bereit, z.B. die Interaktion mit Szenen, deren Node-Hierarchie und Node-Gruppen. Sie können Szenen einfach wechseln oder neu laden, das Spiel beenden, pausieren und fortsetzen. Sie kommt sogar mit interessanten Signalen. Also schauen Sie sich dies an wenn Sie Zeit haben!

Benachrichtigungen

Godot verfügt über ein Benachrichtigungssystem. Diese wird in der Regel nicht für das Schreiben von Skipten benötigt, da auf einem niedrigen Niveau ist und für die meisten Funktionen virtuelle Funktionen zur Verfügung stehen. Trotzdem ist es gut zu wissen, dass dieses Benachrichtigungssystem existiert. Sie können beispielsweise die Funktion Object._notification() in Ihrem Skript einfügen:

func _notification(what):
    match what:
        NOTIFICATION_READY:
            print("This is the same as overriding _ready()...")
        NOTIFICATION_PROCESS:
            print("This is the same as overriding _process()...")
public override void _Notification(int what)
{
    base._Notification(what);

    switch (what)
    {
        case NotificationReady:
            GD.Print("This is the same as overriding _Ready()...");
            break;
        case NotificationProcess:
            var delta = GetProcessDeltaTime();
            GD.Print("This is the same as overriding _Process()...");
            break;
    }
}

Die Dokumentation jeder Klasse in der Class Reference zeigt die Benachrichtigungen an, die sie erhalten kann. In den meisten Fällen bietet GDScript jedoch einfachere überschreibbare Funktionen.

Überschreibbare Funktionen

Solche überschreibbaren Funktionen, welche im Folgenden beschrieben werden, können auf Nodes angewendet werden:

func _enter_tree():
    # When the node enters the Scene Tree, it becomes active
    # and  this function is called. Children nodes have not entered
    # the active scene yet. In general, it's better to use _ready()
    # for most cases.
    pass

func _ready():
    # This function is called after _enter_tree, but it ensures
    # that all children nodes have also entered the Scene Tree,
    # and became active.
    pass

func _exit_tree():
    # When the node exits the Scene Tree, this function is called.
    # Children nodes have all exited the Scene Tree at this point
    # and all became inactive.
    pass

func _process(delta):
    # This function is called every frame.
    pass

func _physics_process(delta):
    # This is called every physics frame.
    pass
public override void _EnterTree()
{
    // When the node enters the Scene Tree, it becomes active
    // and  this function is called. Children nodes have not entered
    // the active scene yet. In general, it's better to use _ready()
    // for most cases.
    base._EnterTree();
}

public override void _Ready()
{
    // This function is called after _enter_tree, but it ensures
    // that all children nodes have also entered the Scene Tree,
    // and became active.
    base._Ready();
}

public override void _ExitTree()
{
    // When the node exits the Scene Tree, this function is called.
    // Children nodes have all exited the Scene Tree at this point
    // and all became inactive.
    base._ExitTree();
}

public override void _Process(float delta)
{
    // This function is called every frame.
    base._Process(delta);
}

public override void _PhysicsProcess(float delta)
{
    // This is called every physics frame.
    base._PhysicsProcess(delta);
}

Wie bereits erwähnt ist es besser, diese Funktionen anstatt des Benachrichtigungssystems zu verwenden.

Nodes erstellen

Um einen Node im Code zu erstellen, rufe die Methode .new() auf, genau wie für jeden anderen klassenbasierten Datentyp auch. Zum Beispiel:

var s
func _ready():
    s = Sprite.new() # Create a new sprite!
    add_child(s) # Add it as a child of this node.
private Sprite _sprite;

public override void _Ready()
{
    base._Ready();

    _sprite = new Sprite(); // Create a new sprite!
    AddChild(_sprite); // Add it as a child of this node.
}

Um ein Node zu löschen, egal ob innerhalb oder außerhalb der Scene, muss free() verwendet werden:

func _someaction():
    s.free() # Immediately removes the node from the scene and frees it.
public void _SomeAction()
{
    _sprite.Free(); // Immediately removes the node from the scene and frees it.
}

Wird ein Node freigegeben, so werden auch all seine untergeordneten Nodes freigegeben. Aus diesem Grund ist das manuelle Löschen von Nodes viel einfacher als es scheint. Geben Sie den Basis-Node frei und alles andere im Teilbaum verschwindet damit.

Es kann vorkommen, dass wir einen Node löschen möchten, der gerade "blockiert" ist, weil er ein Signal sendet oder eine Funktion aufruft. Dies führt zum Absturz des Spiels. Wenn Sie Godot mit dem Debugger ausführen wird dieser Fall häufig erkannt und Sie werden gewarnt.

Die sicherste Methode zum Löschen eines Nodes ist die Verwendung von Node.queue_free(). Dadurch wird der Node im Ruhezustand des Spiels sicher gelöscht.

func _someaction():
    s.queue_free() # Removes the node from the scene and frees it when it becomes safe to do so.
public void _SomeAction()
{
    _sprite.QueueFree(); // Removes the node from the scene and frees it when it becomes safe to do so.
}

Szenen in­s­tan­zi­ie­ren

Szenen werden in zwei Schritten in­s­tan­zi­ie­rt. Zuerst muss die Szene von der Festplatte geladen werden:

var scene = load("res://myscene.tscn") # Will load when the script is instanced.
var scene = GD.Load<PackedScene>("res://myscene.tscn"); // Will load when the script is instanced.

Das Vorladen kann bequemer sein, da dies zur Analysezeit geschieht (gilt nur für GDScript):

var scene = preload("res://myscene.tscn") # Will load when parsing the script.

scene ist noch kein Node. Er ist in einer speziellen Ressource verpackt PackedScene. Um den tatsächlichen Node zu erstellen, muss die Funktion PackedScene.instance() aufgerufen werden. Dies gibt die Struktur der Nodes zurück, die der aktiven Szene hinzugefügt werden können:

var node = scene.instance()
add_child(node)
var node = scene.Instance();
AddChild(node);

Der Vorteil dieses zweistufigen Prozesses besteht darin, dass eine gepackte Szene geladen bleibt und verwendet werden kann, sodass Sie beliebig viele Instanzen erstellen können. Dies ist besonders nützlich um schnell mehrere Feinde, Kugeln und andere Objekte in der aktiven Szene zu instanziieren.

Skripte als Klassen registrieren

Godot verfügt über eine "Skript-Klassen" -Funktion, um einzelne Skripte im Editor zu registrieren. Standardmäßig können Sie nur auf unbenannte Skripte zugreifen, indem Sie die Datei direkt laden.

Sie können ein Skript benennen und als Typ im Editor mit dem Schlüsselwort class_name, gefolgt von dem Namen der Klasse, registrieren. Gefolgt von einem Komma können Sie optional einen Pfad zu einer Bilddatei hinzufügen. Sie finden Ihren neuen Typen dann im Dialogfeld "Nodes- oder Ressourcenerstellung".

extends Node

# Declare the class name here
class_name ScriptName, "res://path/to/optional/icon.svg"

func _ready():
    var this = ScriptName           # reference to the script
    var cppNode = MyCppNode.new()   # new instance of a class named MyCppNode

    cppNode.queue_free()
../../_images/script_class_nativescript_example.png

Warnung

In Godot 3.1:

  • Nur GDScript und Sprachen die NativeScript-fähig sind(z.B. C++) können Skripte registrieren.
  • Ausschließlich GDScript erstellt globale Variablen für jedes benannte Skript.