Wann Szenen, wann Skripte verwenden

Wir haben bereits darüber geredet, was Szenen von Skripten unterscheidet. Skripte definieren eine Engine-Klassenerweiterung mit imperativen Code, Szenen mit deklarativen Code.

Die Fähigkeiten jedes Systems sind daher unterschiedlich. Szenen können definieren, wie eine erweiterte Klasse initialisiert wird, aber nicht, was ihr Verhalten tatsächlich ist. Szenen werden oft in Verbindung mit einem Skript verwendet, so dass die Szene als Erweiterung des deklarativen Codes des Skripts fungiert.

Anonyme Typen

Es ist tatsächlich möglich, den Szeneninhalt komplett in einem Skript zu definieren. Dies ist, in der Essenz, genau das was der Godot Editor tut, ausschließlich aber in den C++Konstruktoren derer Objekte.

Aber die Wahl, welche davon zu verwenden ist, 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.instance() # Different method call
var my_inherited_scene = MyScene.instance(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene
using System;
using Godot;

public class Game : Node
{
    public readonly Script MyNodeScr = (Script)ResourceLoader.Load("MyNode.cs");
    public readonly PackedScene MySceneScn = (PackedScene)ResourceLoader.Load("MyScene.tscn");
    public Node ANode;
    public Node MyNode;
    public Node MyScene;
    public Node MyInheritedScene;

    public Game()
    {
        ANode = new Node();
        MyNode = new MyNode(); // Same syntax
        MyScene = MySceneScn.Instance(); // Different. Instantiated from a PackedScene
        MyInheritedScene = MySceneScn.Instance(PackedScene.GenEditState.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 das Node ist, desto mehr Gründe gibt es, das als Szene zu bauen.

Benannte 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 Symbol angezeigt. In diesen Fällen ist die Fähigkeit des Benutzers, das Skript zu verwenden, viel rationalisierter. Anstatt es...

  1. Kennen Sie den Basistyp des Skripts, das sie verwenden möchten.

  2. Erstellt eine Instanz dieses Basistyps.

  3. Füge das Skript zum Node hinzu.

    1. (Drag-n-Drop-Methode)

      1. Finde das Skript im Dateisystem-Dock.

      2. Benutze Drag and Drop, um das Skript in das Szenen-Dock zu bringen.

    2. (Eigenschaftsmethode)

      1. Scrollen Sie im Inspektor nach unten, um die Eigenschaft Skript zu finden, und wählen Sie sie aus.

      2. Wählen Sie "Laden" aus der Dropdown-Liste.

      3. Wählen Sie das Skript im Dateidialog aus.

Mit einem registrierten Skript wird der geskriptete Typ stattdessen zu einer Erstellungsoption wie die anderen Nodes und Ressourcen im System. Man muss keine der oben genannten Arbeiten durchführen. Der Erstellungsdialog hat sogar eine Suchleiste, um den Typ anhand des Namens zu suchen.

Es gibt zwei Systeme, um Typen zu registrieren...

  • Custom Types

    • Nur 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 Symbol zu definieren.

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

    • Einrichten mit EditorPlugin.add_custom_type.

  • Script Classes

    • Editor und Laufzeit Erreichbarkeit.

    • Zeigt Vererbungsbeziehungen in Gänze an.

    • Erzeugt das 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 Symbol zu definieren.

    • Engine-Entwickler müssen die Unterstützung für Sprachen manuell hinzufügen (sowohl Namensnennung als auch Laufzeitzugriff).

    • Nur Godot 3.1+.

    • 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 Typennamen 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 Funktionen 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.

Leistung 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 Nodehierarchien demonstriert dies. Die Logik eines jeden Nodes kann mehrere hundert Codezeilen lang sein.

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

# Main.gd
extends Node

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

public class Main : Resource
{
    public Node Child { get; set; }

    public Main()
    {
        Child = new Node();
        Child.Name = "Child";
        Child.Script = ResourceLoader.Load<Script>("child.gd");
        Child.Owner = this;
        AddChild(Child);
    }
}

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 Leistungsproblem zu vermeiden. PackedScene, der Basistyp, von dem Szenen erben, definiert Ressourcen, die serialisierte Daten zum Erstellen von Objekten verwenden. Die Engine kann Szenen im Stapel im Back-End verarbeiten und bietet eine viel bessere Leistung als Skripte.

Fazit

Letztendlich ist der beste Ansatz, 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 Könnensstufen 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 dies in 3.1 immer noch tun, indem man eine Skriptklasse deklariert und ihr eine Szene als Konstante gibt. Das Skript wird praktisch zu einem Namespace:

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