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.

Preferenze di logica

Ti sei mai chiesto se dovresti affrontare il problema X con la strategia Y o Z? Questo articolo affronta una varietà di argomenti riguardo questi dilemmi.

Aggiungere nodi e modificarne le proprietà: cosa fare prima?

Quando si inizializzano i nodi da uno script in fase di esecuzione, potrebbe essere necessario modificare proprietà come il nome o la posizione del nodo. Un dilemma comune è: quando è opportuno modificare questi valori?

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.

Per queste ragioni, di solito è consigliabile impostare i valori iniziali di un nodo prima di aggiungerlo all'albero di scene. Esistono alcune eccezioni in cui i valori non possono essere impostati prima di essere aggiunti all'albero di scene, come impostare la posizione globale.

Caricamento (load) vs pre-caricamento (preload)

In GDScript, esiste il metodo globale preload. Esso carica le risorse il prima possibile per anticipare le operazioni di "caricamento" ed evitare di caricare risorse nel mezzo di codice sensibile alle prestazioni.

La sua controparte, il metodo load, carica una risorsa solo quando raggiunge l'istruzione di caricamento. In altre parole, carica una risorsa sul posto, il che può causare rallentamenti quando avviene durante processi sensibili. La funzione load() è anche un alias di ResourceLoader.load(path), accessibile a tutti i linguaggi di scripting.

Allora, quando esattamente avviene il pre-caricamento rispetto al caricamento, e quando si dovrebbe usare l'uno o l'altro? Vediamo un esempio:

# 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")

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:

  1. 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.

  2. Se qualcos'altro potesse sostituire il valore (come l'inizializzazione esportata di una scena), pre-caricare il valore sarebbe inutile. Questo punto non è un fattore significativo se si intende creare sempre lo script autonomamente.

  3. Se si desidera solo "importare" un'altra risorsa di classe (script o scena), utilizzare una costante precaricata è spesso la soluzione migliore. Tuttavia, in casi eccezionali, potrebbe essere opportuno non farlo:

    1. Se la classe "importata" è suscettibile a modifiche, allora dovrebbe essere una proprietà, inizializzata tramite @export o load() (e forse nemmeno inizializzata subito).

    2. 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 null and remove all references to the resource (which, as a RefCounted-extending type, will cause the resources to delete themselves from memory).

Livelli grandi: statico vs dinamico

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?

Beh, la risposta semplice è: "quando le prestazioni lo necessitano". Il dilemma associato alle due opzioni è una delle antiche scelte di programmazione: si ottimizza prima la memoria rispetto alla velocità, oppure viceversa?

La risposta più ingenua è usare un livello statico che carichi tutto in una volta. Ma, a seconda del progetto, questo potrebbe consumare una grande quantità di memoria. Sprecare la RAM degli utenti può rallentare o addirittura arrestare i programmi in esecuzione, a causa di tutte le altre attività che il computer cerca di eseguire contemporaneamente.

In ogni caso, è opportuno suddividere le scene più grandi in scene più piccole (per favorire la riutilizzabilità dei contenuti). Gli sviluppatori possono quindi progettare un nodo che gestisca la creazione/caricamento e l'eliminazione/scaricamento di risorse e nodi in tempo reale. I giochi con ambienti vasti e variegati o elementi generati proceduralmente spesso implementano queste strategie per evitare di sprecare la memoria.

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.

Pertanto, le opzioni migliori sarebbero...

  1. Use static levels for smaller games.

  2. 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.

  3. 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.

Per un esempio dei vari modi in cui è possibile scambiare le scene durante l'esecuzione, consultare la documentazione "Cambiare le scene manualmente".