Ressourcen

Nodes und Ressourcen

Bisher haben wir uns auf die Node Klasse in Godot konzentriert, da dies die Klasse ist, die Du zum Kodieren von Verhalten verwendest. Die meisten Funktionen der Engine sind darauf angewiesen. Ein anderer Datentyp ist genauso wichtig: Resource.

Nodes geben Dir Funktionalität: Sie zeichnen Sprites, 3D-Modelle, simulieren Physik, ordnen Benutzeroberflächen usw. an. Ressourcen * sind **Datencontainer. Sie machen nichts alleine: Stattdessen verwenden Nodes die in Ressourcen enthaltenen Daten.

Alles, was Godot speichert oder von der Festplatte lädt, ist eine Ressource. Sei es eine Szene (eine .tscn- oder eine .scn-Datei), ein Bild, ein Skript … Hier einige Beispele für Resource: Texture, Script, Mesh, Animation, AudioStream, Font, Translation.

Wenn die Engine eine Ressource von der Festplatte lädt, wird sie nur einmal geladen. Wenn sich bereits eine Kopie dieser Ressource im Speicher befindet, wird beim erneuten Laden der Ressource jedes Mal dieselbe Kopie zurückgegeben. Da Ressourcen nur Daten enthalten, müssen sie nicht dupliziert werden.

Jedes Objekt, sei es ein Node oder eine Ressource, kann Eigenschaften exportieren. Es gibt viele Arten von Eigenschaften wie String, Integer (Ganzzahl), Vector2 usw., und jeder dieser Typen kann zu einer Ressource werden. Dies bedeutet, dass sowohl Nodes als auch Ressourcen Ressourcen als Eigenschaften enthalten können:

../../_images/nodes_resources.png

Extern vs. integriert

Es gibt zwei Möglichkeiten, Ressourcen zu speichern. Diese können sein:

  1. Extern zu einer Szene, die als einzelne Dateien auf der Festplatte gespeichert ist.
  2. Integriert gespeichert in der *.tscn- oder *.scn-Datei, an die sie angehängt sind.

Um genauer zu sein, hier ist eine Texture in einem Sprite Node:

../../_images/spriteprop.png

Wenn Du auf die Ressourcenvorschau klickst, kannst Du die Eigenschaften der Ressource anzeigen und bearbeiten.

../../_images/resourcerobi.png

Die Pfadeigenschaft gibt an, woher die Ressource stammt. In diesem Fall kommt sie von einem PNG-Bild namens robi.png. Wenn die Ressource aus einer solchen Datei stammt, handelt es sich um eine externe Ressource. Wenn Du den Pfad löschst oder dieser Pfad leer ist, wird sie zu einer integrierten Ressource.

Der Wechsel zwischen integrierten und externen Ressourcen erfolgt beim Speichern der Szene. Wenn Du im obigen Beispiel den Pfad `“res://robi.png „` löschst und speicherst, speichert Godot das Bild in der .tscn Szenen-Datei.

Bemerkung

Selbst wenn Du eine integrierte Ressource speicherst, lädt die Engine bei einer mehrmaligen Instanzierung einer Szene nur eine Kopie davon.

Laden von Ressourcen via Code

Es gibt zwei Möglichkeiten, Ressourcen im Code zu laden. Erstens kannst Du jederzeit die Funktion load() verwenden:

func _ready():
        var res = load("res://robi.png") # Godot loads the Resource when it reads the line.
        get_node("sprite").texture = res
public override void _Ready()
{
    var texture = (Texture)GD.Load("res://robi.png"); // Godot loads the Resource when it reads the line.
    var sprite = (Sprite)GetNode("sprite");
    sprite.Texture = texture;
}

Du kannst auch preload verwenden, um Ressourcen vorzuladen. Im Gegensatz zu load liest diese Funktion die Datei von der Festplatte und lädt sie erst zur Kompilierzeit. Daher kannst Du preload nicht mit einem variablen Pfad aufrufen: Du musst eine konstante Zeichenkette (String) verwenden.

func _ready():
        var res = preload("res://robi.png") # Godot loads the resource at compile-time
        get_node("sprite").texture = res
// 'preload()' is unavailable in C Sharp.

Laden von Szenen

Szenen sind auch Ressourcen, aber es gibt einen Haken. Auf der Festplatte gespeicherte Szenen sind Ressourcen des Typs PackedScene. Die Szene ist in eine Ressource gepackt.

Um eine Instanz der Szene zu erhalten, musst Du die Methode PackedScene.instance() verwenden.

func _on_shoot():
        var bullet = preload("res://bullet.tscn").instance()
        add_child(bullet)
private PackedScene _bulletScene = (PackedScene)GD.Load("res://bullet.tscn");

public void OnShoot()
{
    Node bullet = _bulletScene.Instance();
    AddChild(bullet);
}

Diese Methode erstellt die Nodes in der Hierarchie der Szene, konfiguriert sie und gibt den Stammnode der Szene zurück. Du kannst es dann als untergeordnetes Element eines anderen Nodes hinzufügen.

Der Ansatz hat mehrere Vorteile. Da die Funktion PackedScene.instance () ziemlich schnell ist, kannst Du neue Feinde, Schüsse, Effekte usw. erstellen, ohne sie jedes Mal erneut von der Festplatte laden zu müssen. Aber denke daran, dass Bilder, Meshes usw. von den Szeneninstanzen immer gemeinsam genutzt werden.

Ressourcen freigeben

Wenn eine Ressource nicht mehr verwendet wird, wird sie automatisch freigegeben. Da in den meisten Fällen Ressourcen in Nodes enthalten sind, gibt die Engine bei der Freigabe eines Nodes alle Ressourcen frei, die ihm gehören, sofern kein anderer Node sie verwendet.

Eigene Ressourcen erstellen

Wie jedes Objekt in Godot können Benutzer auch Ressourcen schreiben. Ressourcenskripte erben die Fähigkeit, frei zwischen Objekteigenschaften und serialisiertem Text oder binären Daten (/.tres, /.res) zu übersetzen. Sie erben auch die Verwaltung des Referenzzählspeichers vom Referenztypen.

Gegenüber alternativen Datenstrukturen, wie JSON-, CSV- oder benutzerdefinierten TXT-Dateien, bietet dies viele entscheidende Vorteile. Benutzer können diese Assets nur als Dictionary (JSON) oder als File zum Analysieren importieren. Was Ressourcen auszeichnet, ist ihre Vererbung von Object, Reference und Resource Funktionen:

  • Du kannst Konstanten definieren, sodass Konstanten aus anderen Datenfeldern oder Objekten nicht benötigt werden.
  • Du kannst Methoden definieren, einschließlich Setter- / Getter-Methoden für Eigenschaften. Dies ermöglicht die Abstraktion und Einkapselung der zugrunde liegenden Daten. Wenn die Struktur des Ressource-Skripts geändert werden muss, muss das Spiel, das die Ressource verwendet, nicht ebenfalls geändert werden.
  • Du kannst Signale definieren, sodass Ressourcen Antworten auf Änderungen in den von ihnen verwalteten Daten auslösen können.
  • Sie verfügen über definierte Eigenschaften, sodass Benutzer zu 100% wissen, dass ihre Daten vorhanden sind.
  • Die automatische Serialisierung und Deserialisierung von Ressourcen ist eine integrierte Funktion der Godot Engine. Benutzer müssen keine benutzerdefinierte Logik implementieren, um die Daten einer Ressourcendatei zu importieren oder zu exportieren.
  • Ressourcen können sogar Teilressourcen rekursiv serialisieren, sodass Benutzer noch ausgefeiltere Datenstrukturen entwerfen können.
  • Benutzer können Ressourcen als versionskontrollen-freundliche Textdateien (*. tres) speichern. Beim Exportieren eines Spiels serialisiert Godot Ressourcendateien als Binärdateien (*.res), um die Geschwindigkeit und Komprimierung zu erhöhen.
  • Der Inspektor der Godot Engine rendert die Ressourcendateien sofort. Daher müssen Benutzer häufig keine eigene Logik implementieren, um ihre Daten zu visualisieren oder zu bearbeiten. Doppelklicke dazu auf die Ressourcendatei im Dateisystemdock oder klicke im Inspektor auf das Ordnersymbol und öffne die Datei im Dialogfeld.
  • Du kannst andere Ressourcentypen als nur die Basisressource erweitern.

Warnung

Resources and Dictionaries are both passed by reference, but only Resources are reference-counted. This means that if a Dictionary is passed between objects and the first object is deleted, all other objects‘ references to the Dictionary will be invalidated. Conversely, Resources will not be freed from memory until all the objects are deleted.

extends Node

class MyObject:
    extends Object
    var dict = {}

func _ready():
    var obj1 = MyObject.new()
    var obj2 = MyObject.new()
    obj1.dict.greeting = "hello"
    obj2.dict = obj1.dict             # 'obj2.dict' now references 'obj1's Dictionary.
    obj1.free()                       # 'obj1' is freed and the Dictionary too!
    print(obj2.dict.greeting)         # Error! 'greeting' index accessed on null instance!

    # To avoid this, we must manually duplicate the Dictionary.
    obj1 = MyObject.new()
    obj1.dict.greeting = "hello"
    obj2.dict = obj1.dict.duplicate() # Now we are passing a copy, not a reference.
    obj1.free()                       # obj2's Dictionary still exists.
    print(obj2.dict.greeting)         # Prints 'hello'.

Godot erleichtert es, im Inspektor benutzerdefinierte Ressourcen zu erstellen.

  1. Erstelle im Inspektor ein einfaches Ressourcenobjekt. Dies kann sogar ein Typ sein, der Ressource ableitet, solange Dein Skript diesen Typ erweitert.
  2. Stelle die Eigenschaft script im Inspektor als Dein Skript ein.

Der Inspektor zeigt jetzt die benutzerdefinierten Eigenschaften Deines Ressourcen-Skripts an. Wenn Du diese Werte bearbeiten und die Ressource speicherst, serialisiert der Inspektor auch die benutzerdefinierten Eigenschaften! Um eine Ressource aus dem Inspektor zu speichern, klicke auf das Menü des Inspektors (oben rechts) und wähle „Speichern“ oder „Speichern unter …“.

Wenn die Skriptsprache Folgendes unterstützt :ref:`script classes <doc_scripting_continued_class_name>, dann wird der Prozess optimiert. Wenn Du nur für Dein Skript einen Namen definieren, wird dieser dem Erstellungsdialog des Inspektors hinzugefügt. Dadurch wird Dein Skript automatisch zu dem von Dir erstellten Ressourcenobjekt hinzugefügt.

Sehen wir uns einige Beispiele an.

# bot_stats.gd
extends Resource
export(int) var health
export(Resource) var sub_resource
export(Array, String) var strings

func _init(p_health = 0, p_sub_resource = null, p_strings = []):
    health = p_health
    sub_resource = p_sub_resource
    strings = p_strings

# bot.gd
extends KinematicBody

export(Resource) var stats

func _ready():
    # Uses an implicit, duck-typed interface for any 'health'-compatible resources.
    if stats:
        print(stats.health) # Prints '10'.
// BotStats.cs
using System;
using Godot;

namespace ExampleProject {
    public class BotStats : Resource
    {
        [Export]
        public int Health { get; set; }

        [Export]
        public Resource SubResource { get; set; }

        [Export]
        public String[] Strings { get; set; }

        public BotStats(int health = 0, Resource subResource = null, String[] strings = null)
        {
            Health = health;
            SubResource = subResource;
            Strings = strings ?? new String[0];
        }
    }
}

// Bot.cs
using System;
using Godot;

namespace ExampleProject {
    public class Bot : KinematicBody
    {
        [Export]
        public Resource Stats;

        public override void _Ready()
        {
            if (Stats != null && Stats is BotStats botStats) {
                GD.Print(botStats.Health); // Prints '10'.
            }
        }
    }
}

Bemerkung

Ressourcenskripte ähneln den ScriptableObjects von Unity. Der Inspektor bietet integrierte Unterstützung für benutzerdefinierte Ressourcen an. Auf Wunsch können Benutzer sogar eigene, auf Controls basierende Werkzeugskripte erstellen und diese mit einem EditorPlugin kombinieren, um benutzerdefinierte Visualisierungen und Editoren für ihre Daten zu erstellen.

Die DataTables und CurveTables der Unreal Engine 4 können mit Ressourcenskripten auch leicht neu erstellt werden. DataTables sind ein String, der einer benutzerdefinierten Struktur zugeordnet ist, ähnlich einem Dictionary, das einen String einem sekundären benutzerdefinierten Ressourcenskript zuordnet.

# bot_stats_table.gd
extends Resource

const BotStats = preload("bot_stats.gd")

var data = {
    "GodotBot": BotStats.new(10), # Creates instance with 10 health.
    "DifferentBot": BotStats.new(20) # A different one with 20 health.
}

func _init():
    print(data)
using System;
using Godot;

public class BotStatsTable : Resource
{
    private Godot.Dictionary<String, BotStats> _stats = new Godot.Dictionary<String, BotStats>();

    public BotStatsTable()
    {
        _stats["GodotBot"] = new BotStats(10); // Creates instance with 10 health.
        _stats["DifferentBot"] = new BotStats(20); // A different one with 20 health.
        GD.Print(_stats);
    }
}

Anstatt nur die Dictionary-Werte einzugeben, können Sie alternativ auch …

  1. eine Wertetabelle aus einer Kalkulationstabelle importieren und diese Schlüsselwertpaare generieren oder …
  2. eine Visualisierung im Editor entwerfen und ein einfaches Plugin erstellen, das beim Öffnen dieser Ressourcentypen zum Inspektor hinzugefügt wird.

CurveTables sind dasselbe wie DataTables, außer dass sie einem Array von Float-Werten (Gleitkommawerten) oder einem Ressourcenobjekt, wie Curve / Curve2D zugeordnet sind.

Warnung

Beachte, dass Ressourcendateien (*.tres /*.res) den Pfad des von Dir verwendeten Skripts in der Datei speichert. Wenn sie geladen sind, werden sie dieses Skript als Erweiterung ihres Typs abrufen und laden. Das bedeutet, dass der Versuch, eine Unterklasse zuzuordnen, d. h. eine innere Klasse eines Skripts (z. B. das Schlüsselwort class ‚in GDScript), nicht funktioniert. Godot wird die benutzerdefinierten Eigenschaften der Skriptunterklasse nicht ordnungsgemäß serialisieren.

Im folgenden Beispiel würde Godot das Node-Skript laden, prüfen, dass es Resource nicht erweitert, und dann feststellen, dass das Skript nicht für das Ressourcenobjekt geladen wurde, da die Typen nicht kompatibel sind.

extends Node

class MyResource:
    extends Resource
    export var value = 5

func _ready():
    var my_res = MyResource.new()

    # This will NOT serialize the 'value' property.
    ResourceSaver.save("res://my_res.tres", my_res)
using Godot;

public class MyNode : Node
{
    public class MyResource : Resource
    {
        [Export]
        public int Value { get; set; } = 5;
    }

    public override void _Ready()
    {
        var res = new MyResource();

        // This will NOT serialize the 'Value' property.
        ResourceSaver.Save("res://MyRes.tres", res);
    }
}