Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Wann Szenen, wann Skripte verwenden

Wir haben bereits darüber gesprochen, was Szenen von Skripten unterscheidet. Skripte definieren eine Engine-Klassenerweiterung mit imperativem Code, Szenen mit deklarativem Code.

Die Fähigkeiten jedes Systems sind daher unterschiedlich. Szenen können definieren, wie eine erweiterte Klasse initialisiert wird, aber nicht, wie ihr Verhalten tatsächlich aussieht. Szenen werden oft in Verbindung mit einem Skript verwendet, wobei die Szene eine Komposition von Nodes deklariert und das Skript das Verhalten mit imperativem Code hinzufügt.

Anonyme Typen

Es ist tatsächlich möglich, den Inhalt einer Szene komplett in einem Skript zu definieren. Dies ist im Wesentlichen das, was der Godot-Editor tut, nur im C++-Konstruktor seiner Objekte.

Aber die Wahl, welchen Inhalt davon man verwenden soll, kann ein Dilemma sein. Das Erstellen von Skript-Instanzen ist identisch mit dem Erstellen von In-Engine-Klassen, während die Handhabung von Szenen eine Änderung der API erfordert:

const MyNode = preload("my_node.gd")
const MyScene = preload("my_scene.tscn")
var node = Node.new()
var my_node = MyNode.new() # Same method call.
var my_scene = MyScene.instantiate() # Different method call.
var my_inherited_scene = MyScene.instantiate(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene.

Außerdem werden Skripte aufgrund der Geschwindigkeitsunterschiede zwischen Engine- und Skriptcode etwas langsamer arbeiten als Szenen. Je größer und komplexer der Node ist, desto mehr Gründe gibt es, ihn als Szene zu bauen.

Benamnte Typen

In einigen Fällen kann ein Benutzer ein Skript als neuen Typ im Editor selbst registrieren. Dadurch wird es als neuer Typ im Node- oder Ressourcenerstellungsdialog mit einem optionalen Icon angezeigt. Auf diese Weise wird die Verwendung des Skripts für den Benutzer wesentlich vereinfacht. Es ersetzt die folgenden Notwendigkeiten...

  1. Den Basistyp des Skript zu kennen, den man verwenden möchte.

  2. Eine Instanz dieses Basistyps zu erstellen.

  3. Das Skript dem Node hinzuzufügen.

Mit einem registrierten Skript wird der geskriptete Typ stattdessen zu einer Erstellungsoption wie die anderen Nodes und Ressourcen im System. Der Erstellungsdialog verfügt sogar über eine Suchleiste, über die der Typ anhand des Namens gesucht werden kann.

Es gibt zwei Systeme für die Registrierung von Typen:

  • Benutzerdefinierte Typen

    • Nur im Editor. Auf Typnamen kann zur Laufzeit nicht zugegriffen werden.

    • Unterstützt keine vererbten benutzerdefinierten Typen.

    • Ein Initialisierungswerkzeug. Erstellt den Node mit dem Skript. Sonst nichts.

    • Der Editor kennt weder den Typ des Skripts noch seine Beziehung zu anderen Enginetypen oder Skripten.

    • Ermöglicht es Benutzern, ein Icon zu definieren.

    • Funktioniert für alle Skriptsprachen, da es sich um Skriptressourcen in abstrakter Sprache handelt.

    • Einrichten mit EditorPlugin.add_custom_type.

  • Skript-Klassen

    • Zugreifbar im Editor und zur Laufzeit.

    • Zeigt Vererbungsbeziehungen komplett an.

    • Erzeugt den Node mit dem Skript, kann aber auch Typen ändern oder den Typ vom Editor aus erweitern.

    • Der Editor kennt die Vererbungsbeziehungen zwischen Skripten, Skriptklassen und Engine-C++ Klassen.

    • Ermöglicht es Benutzern, ein Icon zu definieren.

    • Engine-Entwickler müssen die Unterstützung für Sprachen manuell hinzufügen (sowohl verfügbar machen von Namen als auch Zugriff zur Laufzeit).

    • Nur in Godot 3.1+ verfügbar.

    • Der Editor scannt Projektordner und registriert alle offengelegten Namen für alle Skriptsprachen. Jede Skriptsprache muss ihre eigene Unterstützung für die Bereitstellung dieser Informationen implementieren.

Beide Methoden fügen dem Erstellungsdialog Namen hinzu, aber insbesondere Skriptklassen ermöglichen es dem Benutzer auch, auf den Typnamen zuzugreifen, ohne die Skriptressource zu laden. Das Erstellen von Instanzen und der Zugriff auf Konstanten oder statische Methoden ist von überall aus möglich.

Mit Features wie diesen kann man sich wünschen, dass ihr Typ ein Skript ohne Szene ist, weil es den Benutzern eine einfache Bedienung ermöglicht. Diejenigen, die Plugins entwickeln oder eigene Tools für Designer erstellen, werden es auf diese Weise leichter haben.

Auf der anderen Seite bedeutet dies auch, dass eine weitgehend imperative Programmierung erforderlich ist.

Performance von Script vs PackedScene

Ein letzter Aspekt, der bei der Auswahl von Szenen und Skripten berücksichtigt werden muss, ist die Ausführungsgeschwindigkeit.

Mit zunehmender Größe der Objekte wird auch die notwendige Größe der Skripte zum Erstellen und Initialisieren der Objekte viel größer. Das Erstellen von Node-Hierarchien demonstriert dies. Die Logik eines jeden Nodes kann mehrere hundert Codezeilen lang sein.

Das folgende Codebeispiel erzeugt einen neuen Node, ändert dessen Namen, weist ihm ein Skript zu, setzt seinen zukünftigen Parent als Eigentümer, so dass er zusammen mit diesem auf der Festplatte gespeichert wird, und fügt es schließlich als Child des Main-Nodes hinzu:

# main.gd
extends Node

func _init():
    var child = Node.new()
    child.name = "Child"
    child.script = preload("child.gd")
    add_child(child)
    child.owner = self

Skriptcode wie dieser ist viel langsamer als engineseitiger C++-Code. Jeder Befehl ruft die Skript-API auf, was zu vielen "Suchvorgängen" im Back-End führt, um die auszuführende Logik zu finden.

Szenen helfen, dieses Performance-Problem zu vermeiden. PackedScene, der Basistyp, von dem Szenen erben, definiert Ressourcen, die serialisierte Daten zum Erstellen von Objekten verwenden. Die Engine kann Szenen als Bündel im Back-End verarbeiten und bietet eine viel bessere Performance als Skripte.

Fazit

Letztendlich ist es am besten, Folgendes zu berücksichtigen:

  • Wenn man ein grundlegendes Werkzeug erstellen möchte, das in mehreren verschiedenen Projekten wiederverwendet wird und das wahrscheinlich von Menschen aller Fähigkeitsstufen verwendet wird (einschließlich derer, die sich nicht als "Programmierer" bezeichnen), dann sollte das wahrscheinlich ein Skript sein, am Besten eines mit einem benutzerdefinierten Namen/Symbol.

  • Wenn man ein Konzept erstellen möchte, das für sein Spiel spezifisch ist, sollte es immer eine Szene sein. Szenen sind einfacher zu verfolgen/zu bearbeiten und bieten mehr Sicherheit als Skripte.

  • Wenn man einer Szene einen Namen geben möchte, kann man das immer noch tun, indem man eine Skriptklasse deklariert und ihr eine Szene als Konstante gibt. Das Skript wird sozusagen zu einem Namespace:

    # game.gd
    class_name Game # extends RefCounted, so it won't show up in the node creation dialog.
    extends RefCounted
    
    const MyScene = preload("my_scene.tscn")
    
    # main.gd
    extends Node
    func _ready():
        add_child(Game.MyScene.instantiate())