Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Logik-Präferenzen
Haben Sie sich jemals gefragt, ob man sich Problem X mit Strategie Y oder Z nähern sollte? Dieser Artikel behandelt eine Vielzahl von Themen im Zusammenhang mit diesen Dilemmas.
Nodes hinzufügen und Propertys bearbeiten: Welches zuerst?
Wenn Sie Nodes zur Laufzeit aus einem Skript initialisieren, müssen Sie möglicherweise Propertys wie den Namen oder die Position des Nodes ändern. Ein häufiges Dilemma ist die Frage, wann Sie diese Werte ändern sollten.
It is the best practice to change values on a node before adding it to the scene tree. Some properties' setters have code to update other corresponding values, and that code can be slow! For most cases, this code has no impact on your game's performance, but in heavy use cases such as procedural generation, it can bring your game to a crawl.
For these reasons, it is usually best practice to set the initial values of a node before adding it to the scene tree. There are some exceptions where values can't be set before being added to the scene tree, like setting global position.
Load vs. Preload
In GDScript gibt es die globale Methode preload. Sie lädt Ressourcen so früh wie möglich um die "Lade"-Operationen im Voraus zu auszuführen und somit das Laden von Ressourcen inmitten von leistungsintensivem Code zu vermeiden.
Ihr Gegenstück, die Methode load, lädt eine Ressource erst, wenn sie die Ladeanweisung erreicht. Das heißt, sie lädt eine Ressource "in-place", was zu Verlangsamungen führen kann, wenn dies mitten in empfindlichen Prozessen geschieht. Die Funktion load() ist auch ein Alias für ResourceLoader.load(path), der für alle Skriptsprachen zugänglich ist.
Wann genau erfolgt das Preloading im Vergleich zum Loading und wann sollte man welches davon verwenden? Sehen wir uns ein Beispiel an:
# my_buildings.gd
extends Node
# Note how constant scripts/scenes have a different naming scheme than
# their property variants.
# This value is a constant, so it spawns when the Script object loads.
# The script is preloading the value. The advantage here is that the editor
# can offer autocompletion since it must be a static path.
const BuildingScn = preload("res://building.tscn")
# 1. The script preloads the value, so it will load as a dependency
# of the 'my_buildings.gd' script file. But, because this is a
# property rather than a constant, the object won't copy the preloaded
# PackedScene resource into the property until the script instantiates
# with .new().
#
# 2. The preloaded value is inaccessible from the Script object alone. As
# such, preloading the value here actually does not provide any benefit.
#
# 3. Because the user exports the value, if this script stored on
# a node in a scene file, the scene instantiation code will overwrite the
# preloaded initial value anyway (wasting it). It's usually better to
# provide `null`, empty, or otherwise invalid default values for exports.
#
# 4. Instantiating the script on its own with .new() triggers
# `load("office.tscn")`, ignoring any value set through the export.
@export var a_building : PackedScene = preload("office.tscn")
# Uh oh! This results in an error!
# One must assign constant values to constants. Because `load` performs a
# runtime lookup by its very nature, one cannot use it to initialize a
# constant.
const OfficeScn = load("res://office.tscn")
# Successfully loads and only when one instantiates the script! Yay!
var office_scn = load("res://office.tscn")
using Godot;
// C# and other languages have no concept of "preloading".
public partial class MyBuildings : Node
{
//This is a read-only field, it can only be assigned when it's declared or during a constructor.
public readonly PackedScene Building = ResourceLoader.Load<PackedScene>("res://building.tscn");
public PackedScene ABuilding;
public override void _Ready()
{
// Can assign the value during initialization.
ABuilding = GD.Load<PackedScene>("res://Office.tscn");
}
}
using namespace godot;
class MyBuildings : public Node {
GDCLASS(MyBuildings, Node)
public:
const Ref<PackedScene> building = ResourceLoader::get_singleton()->load("res://building.tscn");
Ref<PackedScene> a_building;
virtual void _ready() override {
// Can assign the value during initialization.
a_building = ResourceLoader::get_singleton()->load("res://office.tscn");
}
};
Preloading allows the script to handle all the loading the moment one loads the script. Preloading is useful, but there are also times when one doesn't wish to use it. Here are a few considerations when determining which to use:
If one cannot determine when the script might load, then preloading a resource (especially a scene or script) could result in additional loads one does not expect. This could lead to unintentional, variable-length load times on top of the original script's load operations.
Wenn etwas anderes den Wert ersetzen könnte (wie die exportierte Initialisierung einer Szene), hat das Preloading des Werts keine Bedeutung. Dieser Punkt ist nicht von Bedeutung, wenn man beabsichtigt, das Skript immer selbst zu erstellen.
Wenn man nur eine andere Klassenressource (Skript oder Szene) "importieren" möchte, ist die Verwendung einer vorher geladenen Konstante oft die beste Vorgehensweise. In Ausnahmefällen sollte man dies jedoch nicht tun:
Wenn sich die 'importierte' Klasse ändern kann, dann sollte sie stattdessen eine Property sein, die entweder mit
@exportoderload()initialisiert wird (und vielleicht sogar erst später initialisiert wird).If the script requires a great many dependencies, and one does not wish to consume so much memory, then one may wish to load and unload various dependencies at runtime as circumstances change. If one preloads resources into constants, then the only way to unload these resources would be to unload the entire script. If they are instead loaded as properties, then one can set these properties to
nulland remove all references to the resource (which, as a RefCounted-extending type, will cause the resources to delete themselves from memory).
Große Levels: statisch vs. dynamisch
If one is creating a large level, which circumstances are most appropriate? Is it better to create the level as one static space? Or is it better to load the level in pieces and shift the world's content as needed?
Nun, die einfache Antwort lautet: "wenn die Performance es erfordert". Das Dilemma, das mit den beiden Optionen verbunden ist, ist eine der uralten Programmierentscheidungen: Optimiert man den Speicher gegenüber der Laufzeit oder umgekehrt?
Die naive Antwort besteht darin, eine statische Ebene zu verwenden, die alles auf einmal lädt. Dies kann jedoch je nach Projekt viel Speicherplatz beanspruchen. Die Vergeudung von Arbeitsspeicher führt dazu, dass Programme langsam laufen oder sogar abstürzen, weil sich der Computer gleichzeitig mit anderen Aufgaben beschäftigt.
Egal was passiert, man sollte größere Szenen in kleinere aufteilen (um die Wiederverwendbarkeit von Assets zu erleichtern). Entwickler können dann einen Node entwerfen, der das Erstellen/Laden und Löschen/Entladen von Ressourcen und Nodes in Echtzeit verwaltet. Spiele mit großen und unterschiedlichen Umgebungen oder prozedural generierten Elementen implementieren diese Strategien häufig, um Speicherverschwendung zu vermeiden.
On the flip side, coding a dynamic system is more complex; it uses more programmed logic which results in opportunities for errors and bugs. If one isn't careful, they can develop a system that bloats the technical debt of the application.
Die besten Optionen wären demnach...
Use static levels for smaller games.
If one has the time/resources on a medium/large game, create a library or plugin that can manage nodes and resources with code. If refined over time so as to improve usability and stability, then it could evolve into a reliable tool across projects.
Use dynamic logic for a medium/large game because one has the coding skills, but not the time or resources to refine the code (game's gotta get done). Could potentially refactor later to outsource the code into a plugin.
Ein Beispiel für die verschiedenen Möglichkeiten, wie man Szenen zur Laufzeit austauschen kann, finden Sie in der Dokumentation "Szenen manuell ändern".