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.

Riferimento GDScript

GDScript è un linguaggio di programmazione di alto livello, orientato agli oggetti, imperativo e gradualmente tipizzato, creato per Godot. Utilizza una sintassi basata sull'indentazione simile a linguaggi come Python. Il suo obiettivo è essere ottimizzato e strettamente integrato con Godot Engine, consentendo una grande flessibilità per la creazione e integrazione di contenuti.

GDScript è completamente indipendente da Python e non si basa su di esso.

Storia

Nota

La documentazione sulla storia di GDScript è stata spostata in Domande frequenti.

Esempio di GDScript

Alcune persone imparano meglio dando un'occhiata alla sintassi, quindi ecco un semplice esempio di come appare GDScript.

# Everything after "#" is a comment.
# A file is a class!

# (optional) icon to show in the editor dialogs:
@icon("res://path/to/optional/icon.svg")

# (optional) class definition:
class_name MyClass

# Inheritance:
extends BaseClass


# Member variables.
var a = 5
var s = "Hello"
var arr = [1, 2, 3]
var dict = {"key": "value", 2: 3}
var other_dict = {key = "value", other_key = 2}
var typed_var: int
var inferred_type := "String"

# Constants.
const ANSWER = 42
const THE_NAME = "Charly"

# Enums.
enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY}
enum Named {THING_1, THING_2, ANOTHER_THING = -1}

# Built-in vector types.
var v2 = Vector2(1, 2)
var v3 = Vector3(1, 2, 3)


# Function, with a default value for the last parameter.
func some_function(param1, param2, param3 = 123):
    const local_const = 5

    if param1 < local_const:
        print(param1)
    elif param2 > 5:
        print(param2)
    else:
        print("Fail!")

    for i in range(20):
        print(i)

    while param2 != 0:
        param2 -= 1

    match param3:
        3:
            print("param3 is 3!")
        _:
            print("param3 is not 3!")

    var local_var = param1 + 3
    return local_var


# Functions override functions with the same name on the base/super class.
# If you still want to call them, use "super":
func something(p1, p2):
    super(p1, p2)


# It's also possible to call another function in the super class:
func other_something(p1, p2):
    super.something(p1, p2)


# Inner class
class Something:
    var a = 10


# Constructor
func _init():
    print("Constructed!")
    var lv = Something.new()
    print(lv.a)

Se hai avuto esperienze con linguaggi staticamente tipizzati come C, C++, o C# ma non hai mai usato un linguaggio dinamicamente tipizzato, è consigliato che tu legga questo tutorial: GDScript: Un'introduzione ai linguaggi dinamici.

Identificatori

Una qualsiasi stringa che si limita solo a caratteri alfabetici (dalla a alla z e dalla A alla Z), cifre (da 0 a 9) e _ può essere un identificatore. Inoltre, gli identificatori non devono iniziare con una cifra. Gli identificatori fanno distinzione tra maiuscole e minuscole (foo è diverso da FOO).

Gli identificatori possono anche contenere la maggior parte dei caratteri Unicode che fanno parte di UAX#31. Ciò consente di utilizzare nomi di identificatori scritti in lingue diverse dall'inglese. I caratteri Unicode considerati "confondibili" con i caratteri ASCII, nonché le emoji, non sono ammessi negli identificatori.

Parole chiave

Di seguito è riportato l'elenco di parole chiave supportate dal linguaggio. Siccome le parole chiave sono riservate (token), non si possono usare come identificatori. Gli operatori (come in, not, and o or) e i nomi dei tipi integrati, elencati nelle prossime sezioni, sono anch'essi riservati.

Le parole chiave sono definite nel tokenizer di GDScript in caso si voglia dare un'occhiata in dettaglio.

Parola chiave

Descrizione

if

Vedere if/else/elif.

elif

Vedere if/else/elif.

else

Vedere if/else/elif.

for

Vedere for.

while

Vedere while.

match

Vedere match.

when

Utilizzato dalle guardie di schema nelle istruzioni match.

break

Esce dall'esecuzione del ciclo for o while attuale.

continue

Salta immediatamente alla prossima iterazione del ciclo for o while.

pass

Usato quando un istruzione è richiesta sintatticamente ma non si desidera che del codice venga eseguito, per esempio in una funzione vuota.

return

Restituisce un valore da una funzione.

class

Definisce una classe interna. Vedere Inner classes.

class_name

Definisce lo script come una classe accessibile globalmente con il nome specificato. Consultare Registering named classes.

extends

Definisce quale classe estendere con la classe attuale.

is

Verifica se una variabile estende una determinata classe, o è di un determinato tipo integrato.

in

Verifica se un valore si trova all'interno di una stringa, un array, un intervallo, un dizionario o un nodo. Se utilizzato con for, li itera invece di verificarli.

as

Converte il valore in un certo tipo, se possibile.

self

Si riferisce all'istanza della classe attuale. Vedere self.

super

Risolve l'ambito del metodo padre. Vedere Inheritance.

signal

Definisce un segnale. Vedere Signals.

func

Definisce una funzione. Vedere Functions.

static

Definisce una funzione statica o una variabile membro statica.

const

Definisce una costante. Vedere Constants.

enum

Definisce un enumerazione. Vedere Enums.

var

Definisce una variabile. Vedere Variables.

breakpoint

Ausilio per i punti d'interruzione del debugger nell'editor. A differenza dei punti d'interruzione creati cliccando nella barra laterale, breakpoint è memorizzato nello script stesso. Ciò lo rende persistente su macchine diverse quando si utilizza il controllo versione.

preload

Precarica una classe o una variabile. Vedere Classes as resources.

await

Attende il completamento di un segnale o di una coroutine. Vedere Awaiting signals or coroutines.

yield

Precedentemente utilizzato per le coroutine. Conservato come parola chiave per il momento della transizione.

assert

Asserisce una condizione, registra l'errore in caso di fallimento. Ignorato nelle build non di debug. Vedere Assert keyword.

void

Utilizzato per indicare che una funzione non restituisce alcun valore.

PI

Costante PI.

TAU

Costante TAU.

INF

Costante di infinito. Usata per confronti e come risultato di calcoli.

NAN

Costante NAN ("Not A Number", non un numero). Utilizzato come risultato quando i calcoli sono impossibili.

Operatori

Di seguito è riportato l'elenco degli operatori supportati e la loro precedenza. Tutti gli operatori binari sono associativi a sinistra, incluso l'operatore **. Ciò significa che 2 ** 2 ** 3 è uguale a (2 ** 2) ** 3. Utilizzare le parentesi per specificare esplicitamente la precedenza desiderata, ad esempio 2 ** (2 ** 3). L'operatore ternario if/else è associativo a destra.

Operatore

Descrizione

( )

Raggruppamento (priorità più alta)

Le parentesi non sono proprio un operatore, ma consentono di specificare esplicitamente la precedenza di un'operazione.

x[index]

Abbonamento

x.attribute

Riferimento di attributo

foo()

Chiamata di funzione

await x

Awaiting signals or coroutines

x is Node
x is not Node

Verifica di tipo

Vedere anche la funzione is_instance_of().

x ** y

Potenza

Moltiplica x per se stesso y volte, in modo simile alla chiamata della funzione pow().

~x

Operazione bit a bit NOT

+x
-x

Identità / negazione

x * y
x / y
x % y

Moltiplicazione / Divisione / Resto

L'operatore % è utilizzato anche per formattare le stringhe.

Nota: Questi operatori hanno lo stesso comportamento di C++, il che potrebbe risultare inaspettato per gli utenti che provengono da Python, JavaScript, ecc. Vedere la nota dettagliata dopo la tabella.

x + y
x - y

Addizione (o Concatenazione) / Sottrazione

x << y
x >> y

Spostamento di bit

x & y

Operazione bit a bit AND

x ^ y

Operazione bit a bit XOR

x | y

Operazione bit a bit OR

x == y
x != y
x < y
x > y
x <= y
x >= y

Confronto

Vedere la nota dettagliata dopo la tabella.

x in y
x not in y

Verifica di inclusione

in è utilizzato anche con la parola chiave for come parte della sintassi.

not x
!x

NOT booleano e il suo alias non consigliato

x and y
x && y

AND booleano e il suo alias non consigliato

x or y
x || y

OR booleano e il suo alias non consigliato

true_expr if cond else false_expr

if / else ternario

x as Node

Conversione di tipo

x = y
x += y
x -= y
x *= y
x /= y
x **= y
x %= y
x &= y
x |= y
x ^= y
x <<= y
x >>= y

Assegnazione (priorità più bassa)

Non è possibile utilizzare un operatore di assegnazione all'interno di un'espressione.

Nota

Il comportamento di alcuni operatori potrebbe differire da quello previsto:

  1. Se entrambi gli operandi dell'operatore / sono int, viene effettuata una divisione intera anziché una divisione frazionaria. Ad esempio 5 / 2 == 2, non 2.5. Se ciò non è desiderato, utilizzare almeno un float (x / 2.0), una conversione di tipo (float(x) / y) o moltiplicare per 1.0 (x * 1.0 / y).

  2. L'operatore % è disponibile solo per gli int, per i float utilizzare la funzione fmod().

  3. Per valori negativi, l'operatore % e fmod() utilizzano il troncamento invece di arrotondare verso meno infinito. Ciò significa che il resto ha un segno. Se è necessario il resto in senso matematico, utilizzare invece le funzioni posmod() e fposmod().

  4. Gli operatori == e != a volte consentono di confrontare valori di tipi diversi (ad esempio, 1 == 1.0 è vero), ma in altri casi possono causare un errore all'esecuzione. In caso di dubbi sui tipi degli operandi, è possibile utilizzare in modo sicuro la funzione is_same() (notando però che è più stringente su tipi e riferimenti). Per confrontare numeri in virgola mobile, utilizzare invece le funzioni is_equal_approx() e is_zero_approx().

Letterali

Esempio(i)

Descrizione

null

Valore nullo

false, true

Valori booleani

45

Numero intero in base 10 (decimale)

0x8f51

Numero intero in base 16 (esadecimale)

0b101010

Numero intero in base 2 (binario)

3.14, 58.1e-10

Numero a virgola mobile (reale)

"Ciao", 'Salve'

Stringhe regolari

"""Ciao""", '''Salve'''

Stringhe regolari a virgolette triple

r"Ciao", r'Salve'

Stringhe grezze

r"""Ciao""", r'''Salve'''

Stringhe grezze a triple virgolette

&"name"

StringName

^"Node/Label"

NodePath

Ci sono anche due costrutti che sembrano letterali, ma in realtà non lo sono:

Esempio

Descrizione

$NodePath

Abbreviazione di get_node("NodePath")

%UniqueNode

Abbreviazione di get_node("%UniqueNode")

I numeri interi e in virgola mobile si possono suddividere con _ per renderli più leggibili. I seguenti modi per scrivere numeri sono tutti validi:

12_345_678  # Equal to 12345678.
3.141_592_7  # Equal to 3.1415927.
0x8080_0000_ffff  # Equal to 0x80800000ffff.
0b11_00_11_00  # Equal to 0b11001100.

Le stringhe letterali regolari possono contenere le seguenti sequenze di escape:

Sequenza di escape

Si espande a

\n

Nuova riga (avanzamento di riga)

\t

Carattere di tabulazione orizzontale

\r

Ritorno a capo

\a

Avviso (bip/campanello)

\b

Cancella indietro

\f

Salto pagina (Formfeed)

\v

Carattere di tabulazione verticale

\"

Doppie virgolette

\'

Singola virgoletta

\\

Barra rovesciata

\uXXXX

Codice Unicode UTF-16 XXXX (esadecimale, senza distinzione tra maiuscole e minuscole)

\UXXXXXX

Codice Unicode UTF-32 XXXXXX (esadecimale, senza distinzione tra maiuscole e minuscole)

Esistono due modi per rappresentare un carattere Unicode con escape superiore a 0xFFFF:

Inoltre, all'interno di una stringa, utilizzando \ seguito da un carattere di nuova riga, è possibile continuare nella riga successiva, senza inserire un carattere di nuova riga nella stringa stessa.

Una stringa racchiusa tra virgolette di un tipo (ad esempio ") può contenere virgolette di un altro tipo (ad esempio '`) senza escape. Le stringhe tra virgolette triple consentono di evitare l'escape di un massimo di due virgolette consecutive dello stesso tipo (a meno che non siano adiacenti ai bordi della stringa).

Le stringhe letterali grezze codificano sempre la stringa così come appare nel codice sorgente. Questo è particolarmente utile per le espressioni regolari. Una stringa letterale grezza non elabora le sequenze di escape, tuttavia riconosce \\ e \" (\') e le sostituisce con se stessa. Pertanto, una stringa può avere una virgoletta che corrisponde a quella iniziale, ma solo se preceduta da una barra rovesciata.

print("\tchar=\"\\t\"")  # Prints `    char="\t"`.
print(r"\tchar=\"\\t\"") # Prints `\tchar=\"\\t\"`.

Nota

Alcune stringhe non si possono rappresentare tramite letterali di stringhe grezze: non è possibile avere un numero dispari di barre rovesciate alla fine di una stringa, o un apice di apertura senza escape all'interno di una stringa. Tuttavia, in pratica questo non ha importanza, poiché è possibile utilizzare un tipo di apice diverso o la concatenazione con un normale letterale di stringa.

GDScript supporta anche la formattazione di stringhe.

Annotazioni

Le annotazioni sono token speciali in GDScript che agiscono come modificatori di uno script, una dichiarazione, un'istruzione, o una posizione nel codice sorgente. Le annotazioni possono influenzare il modo in cui lo script è trattato dall'editor di Godot e il compilatore di GDScript.

Ogni annotazione comincia con il carattere @ ed è specificata da un nome. Una descrizione dettagliata e un esempio per ogni annotazione sono disponibili nel riferimento classe di GDScript.

Ad esempio, è possibile utilizzarle per esportare un valore nell'editor:

@export_range(1, 100, 1, "or_greater")
var ranged_var: int = 50

Per ulteriori informazioni sull'esportazione delle proprietà, leggere l'articolo Esportazioni in GDScript.

Qualsiasi espressione costante compatibile con il tipo di argomento richiesto si può passare come valore di un argomento di un'annotazione:

const MAX_SPEED = 120.0

@export_range(0.0, 0.5 * MAX_SPEED)
var initial_speed: float = 0.25 * MAX_SPEED

Le annotazioni si possono specificare una per riga o tutte sulla stessa riga. Influiscono sull'istruzione successiva che non è un'annotazione. Le annotazioni possono ricevere argomenti tra parentesi e separati da virgole.

Entrambi i seguenti sono equivalenti:

@annotation_a
@annotation_b
var variable

@annotation_a @annotation_b var variable

L'annotazione @onready

Quando si utilizzano i nodi, è comune voler mantenere i riferimenti a parti della scena in una variabile. Poiché ci si aspetta che le scene siano configurate solo quando entrano nell'albero di scene attivo, i sotto-nodi si possono ottenere solo quando Node._ready() viene chiamato.

var my_label


func _ready():
    my_label = get_node("MyLabel")

Ciò può diventare un po' fastidioso, soprattutto quando nodi e riferimenti esterni si accumulano. Per questo motivo, GDScript include l'annotazione @onready, che rimanda l'inizializzazione di una variabile membro finché _ready() non viene chiamato. Può sostituire il codice precedente con una singola riga:

@onready var my_label = get_node("MyLabel")

Avvertimento

Applicare @onready e una qualsiasi annotazione @export alla stessa variabile non funziona come ci si aspetterebbe. L'annotazione @onready imposterà il valore predefinito dopo che @export abbia effetto, sovrascrivendolo:

@export var a = "init_value_a"
@onready @export var b = "init_value_b"

func _init():
    prints(a, b) # init_value_a <null>

func _notification(what):
    if what == NOTIFICATION_SCENE_INSTANTIATED:
        prints(a, b) # exported_value_a exported_value_b

func _ready():
    prints(a, b) # exported_value_a init_value_b

Pertanto, viene generato l'avviso ONREADY_WITH_EXPORT, il quale viene trattato come un errore per impostazione predefinita. Si sconsiglia di disabilitarlo o ignorarlo.

Commenti

Tutto ciò si trova tra un # e la fine della riga è ignorato e considerato un commento.

# This is a comment.

Suggerimento

Nell'editor di script Godot, le parole chiave speciali vengono evidenziate nei commenti per attirare l'attenzione dell'utente su commenti specifici:

  • Critici (appaiono in rosso): ALERT, ATTENTION, CAUTION, CRITICAL, DANGER, SECURITY

  • Avvisi (appaiono in giallo): BUG, DEPRECATED, FIXME, HACK, TASK, TBD, TODO, WARNING

  • Informazioni (appaiono in verde): INFO, NOTE, NOTICE, TEST, TESTING

Queste parole chiave sono sensibili alle maiuscole e alle minuscole, quindi si devono scrivere in maiuscolo affinché siano riconosciute:

# In the example below, "TODO" will appear in yellow by default.
# The `:` symbol after the keyword is not required, but it's often used.

# TODO: Add more items for the player to choose from.

L'elenco delle parole chiave evidenziate e i relativi colori si possono modificare nella sezione Editor di testo > Tema > Indicatori di commenti delle Impostazioni dell'editor.

Utilizza due simboli cancelletto (##) invece di uno (#) per aggiungere un commento di documentazione, il quale apparirà nella documentazione dello script e nella descrizione dell'ispettore di una variabile esportata. I commenti di documentazione si devono inserire direttamente sopra un elemento documentabile (ad esempio una variabile membro) o all'inizio di un file. Sono disponibili anche opzioni di formattazione dedicate. Consultare Commenti di documentazione in GDScript per i dettagli.

## This comment will appear in the script documentation.
var value

## This comment will appear in the inspector tooltip, and in the documentation.
@export var exported_value

Regioni di codice

Le regioni di codice sono tipi speciali di commenti che l'editor di script interpreta come regioni riducibili. Ciò significa che, dopo aver scritto commenti di regione di codice, è possibile ridurre ed espandere la regione cliccando sulla freccia che appare a sinistra del commento. Questa freccia appare all'interno di un quadrato viola per essere distinguibile dalla riduzione standard del codice.

La sintassi è la seguente:

# Important: There must be *no* space between the `#` and `region` or `endregion`.

# Region without a description:
#region
...
#endregion

# Region with a description:
#region Some description that is displayed even when collapsed
...
#endregion

Suggerimento

Per creare rapidamente una regione di codice, selezionare più righe nell'editor di script, fare clic destro sulla selezione e scegliere Crea regione di codice. La descrizione della regione verrà selezionata automaticamente per la modifica.

È possibile annidare regioni di codice all'interno di altre regioni di codice.

Ecco un esempio concreto di utilizzo delle regioni di codice:

# This comment is outside the code region. It will be visible when collapsed.
#region Terrain generation
# This comment is inside the code region. It won't be visible when collapsed.
func generate_lakes():
    pass

func generate_hills():
    pass
#endregion

#region Terrain population
func place_vegetation():
    pass

func place_roads():
    pass
#endregion

Ciò può essere utile per organizzare grandi porzioni di codice in sezioni più facili da comprendere. Tuttavia, tenere presente che gli editor esterni generalmente non supportano questa funzionalità, quindi assicurarsi che il proprio codice sia facile da seguire anche quando non si dipende sulla riduzione delle regioni di codice.

Nota

Le singole funzioni e le sezioni indentate (come if e for) si possono sempre ridurre nell'editor di script. Ciò significa che si dovrebbe evitare di utilizzare una regione di codice per contenere una singola funzione o sezione indentata, poiché non porterà grandi benefici. Le regioni di codice funzionano meglio quando si utilizzano per raggruppare più elementi.

Continuazione di riga

Una riga di codice in GDScript può essere continuata sulla riga successiva utilizzando una barra rovesciata (\). Aggiungendone una alla fine di una riga, il codice sulla riga successiva si comporterà come se continuasse dalla riga precedente. Ecco un esempio:

var a = 1 + \
2

Una riga può essere continuata più volte in questo modo:

var a = 1 + \
4 + \
10 + \
4

Tipi integrati

I tipi integrati sono allocati nello stack. Vengono passati per valore. Ciò significa che una copia viene creata a ogni assegnazione o quando vengono passati come argomenti alle funzioni. Le eccezioni sono Object, Array, Dictionary e array impacchettati (come PackedByteArray), che vengono passati per riferimento e quindi condivisi. Tutti gli array, Dictionary e alcuni oggetti (Node, Resource) hanno un metodo duplicate() che consente di crearne una copia.

Tipi integrati di base

Una variabile in GDScript si può assegnare a diversi tipi integrati.

null

null è un tipo di dati vuoto che non contiene informazioni e non può esservi assegnato alcun altro valore.

Solo i tipi che ereditano da Object possono avere un valore null (Object è quindi definito un tipo "nullable"). I tipi Variant devono avere sempre un valore valido e quindi non possono avere un valore null.

bool

Abbreviazione di "booleano", può contenere solo true o false.

int

Abbreviazione di "intero", memorizza numeri interi (positivi e negativi). È memorizzato come valore a 64 bit, equivalente a int64_t in C++.

float

Memorizza numeri reali (con decimali) utilizzando valori in virgola mobile (o fluttuante). È memorizzato come valore a 64 bit, equivalente a double in C++. Nota: attualmente, strutture dati come Vector2, Vector3 e PackedFloat32Array memorizzano valori float a precisione singola a 32 bit.

String

Una sequenza di caratteri in formato Unicode.

StringName

Una stringa immutabile che consente una sola istanza di ciascun nome. Sono più lente da creare e possono causare blocchi per il multithreading. In cambio, sono molto veloci da confrontare, il che le rende delle buone candidate per le chiavi di un dizionario.

NodePath

Un percorso pre-analizzato verso un nodo o una proprietà di un nodo. Può essere facilmente assegnato a una stringa e viceversa. Sono utili per interagire con l'albero per ottenere un nodo o per influenzare proprietà come con Tweens.

Tipi vettoriali integrati

Vector2

Tipo di vettore 2D contenente i campi x e y. Vi si può accedere anche come un array.

Vector2i

Uguale a Vector2, ma le componenti sono intere. Utile per rappresentare elementi in una griglia 2D.

Rect2

Tipo di rettangolo 2D contenente due campi di tipo vettore: position e size. Contiene anche un campo end che corrisponde a position + size.

Vector3

Tipo di vettore 3D contenente i campi x, y e z. Anche questo si può accedere come un array.

Vector3i

Uguale a Vector3, ma con componenti interi. Utile per indicizzare elementi in una griglia 3D.

Transform2D

Matrice 3×2 utilizzata per le trasformazioni 2D.

Plane

Tipo di piano 3D in forma normalizzata che contiene un campo di tipo vettore normal e una distanza scalare d.

Quaternion

Il quaternione è un tipo di dato usato per rappresentare una rotazione 3D. È utile per interpolare rotazioni.

AABB

Bounding box (o riquadro 3D) allineato agli assi contiene 2 campi di tipo vettore: position e size. Contiene anche un campo end che corrisponde a position + size.

Basis

Matrice 3x3 utilizzata per rotazioni e cambi di scala in 3D. Contiene 3 campi di tipo vettore (x, y e z) e vi si può anche accedere come un array di vettori 3D.

Transform3D

Trasformazione 3D contenente un campo di tipo Basis basis e un campo di tipo Vector3 origin.

Tipi integrati del motore

Color

Il tipo di dati Color contiene i campi r, g, b e a. Vi si può anche accedere come h, s e v per tonalità/saturazione/valore.

RID

ID risorsa (RID). I server usano RID generici per fare riferimento a dati opachi.

Object

Classe base per tutto ciò che non è un tipo integrato.

Tipi integrati contenitori

Array

Sequenza generica di tipi arbitrari di oggetti, inclusi altri array o dizionari (vedere sotto). L'array si ridimensiona dinamicamente. Gli array sono indicizzati a partire dall'indice 0. Gli indici negativi contano dalla fine.

var arr = []
arr = [1, 2, 3]
var b = arr[1] # This is 2.
var c = arr[arr.size() - 1] # This is 3.
var d = arr[-1] # Same as the previous line, but shorter.
arr[0] = "Hi!" # Replacing value 1 with "Hi!".
arr.append(4) # Array is now ["Hi!", 2, 3, 4].

Array tipizzati

Godot supporta anche gli array tipizzati. Durante le operazioni di scrittura, Godot verifica che i valori degli elementi corrispondano al tipo specificato, in modo che l'array non contenga valori non validi. L'analizzatore statico di GDScript tiene conto degli array tipizzati, tuttavia metodi di array come front() e back() restituiranno comunque il tipo Variant.

Gli array tipizzati hanno la sintassi Array[Type], dove Type può essere qualsiasi tipo Variant, classe nativa o utente, o enumerazione. Gli array tipizzati annidati (come Array[Array[int]]) non sono supportati.

var a: Array[int]
var b: Array[Node]
var c: Array[MyClass]
var d: Array[MyEnum]
var e: Array[Variant]

Array e Array[Variant] sono la stessa cosa.

Nota

Gli array vengono passati per riferimento, quindi il tipo di elementi nell'array è anche un attributo della struttura in memoria a cui una variabile fa riferimento in fase di esecuzione. Il tipo statico di una variabile limita le strutture a cui può fare riferimento. Pertanto, non è possibile assegnare un array con un tipo di elementi diverso, anche se il tipo è un sottotipo del tipo richiesto.

Se si desidera convertire un array tipizzato, si può creare un nuovo array e utilizzare il metodo Array.assign():

var a: Array[Node2D] = [Node2D.new()]

# (OK) You can add the value to the array because `Node2D` extends `Node`.
var b: Array[Node] = [a[0]]

# (Error) You cannot assign an `Array[Node2D]` to an `Array[Node]` variable.
b = a

# (OK) But you can use the `assign()` method instead. Unlike the `=` operator,
# the `assign()` method copies the contents of the array, not the reference.
b.assign(a)

Una unica eccezione è stata fatta per il tipo Array (Array[Variant]), per comodità dell'utente e per compatibilità con il codice vecchio. Nonostante ciò, le operazioni su array non tipizzati sono considerate non sicure.

Array impacchettati

Gli array impacchettati (PackedArray) sono generalmente più veloci da iterare e modificare rispetto a un Array tipizzato dello stesso tipo (ad esempio, PackedInt64Array rispetto a Array[int]) e consumano meno memoria. Nel peggior caso, si prevede che siano veloci quanto un Array non tipizzato. D'altra parte, gli Array non-impacchettati (tipizzati o meno) offrono metodi aggiuntivi come Array.map, che non sono disponibili per i PackedArray. Consultare il riferimento classi per i dettagli sui metodi disponibili. Gli Array tipizzati sono generalmente più veloci da iterare e modificare rispetto agli Array non tipizzati.

Sebbene tutti gli array possano causare frammentazione della memoria quando sono abbastanza grandi, se l'utilizzo della memoria e le prestazioni (velocità di iterazione e modifica) sono un problema e il tipo di dati memorizzati è compatibile con uno dei tipi di array Packed, utilizzare quest'ultimi potrebbe essere meglio. Tuttavia, se non si hanno tali problemi (ad esempio, se la dimensione dell'array non raggiunge le decine di migliaia di elementi), è probabilmente più utile utilizzare array normali o tipizzati, in quanto forniscono metodi pratici che possono semplificare la scrittura e la manutenzione del codice (e potenzialmente velocizzarli se i dati richiedono spesso tali operazioni). Se i dati archiviati sono di un tipo noto (incluse le classi definite dall'utente), è preferibile utilizzare un array tipizzato, in quanto potrebbe migliorare le prestazioni di iterazione e di modifica rispetto a un array non tipizzato.

Dictionary

Contenitore associativo che contiene valori referenziati da chiavi uniche.

var d = {4: 5, "A key": "A value", 28: [1, 2, 3]}
d["Hi!"] = 0
d = {
    22: "value",
    "some_key": 2,
    "other_key": [2, 3, 4],
    "more_key": "Hello"
}

È supportata anche la sintassi di tabelle in stile Lua. Lo stile Lua usa = invece di : e non usa virgolette per indicare le chiavi di tipo stringa (rendendo il testo leggermente più breve). Tuttavia, le chiavi scritte in questa forma non possono iniziare con una cifra (come qualsiasi identificatore in GDScript) e devono essere stringhe letterali.

var d = {
    test22 = "value",
    some_key = 2,
    other_key = [2, 3, 4],
    more_key = "Hello"
}

Per aggiungere una chiave a un dizionario esistente, accedervi come una chiave esistente e assegnargli un valore:

var d = {} # Create an empty Dictionary.
d.waiting = 14 # Add String "waiting" as a key and assign the value 14 to it.
d[4] = "hello" # Add integer 4 as a key and assign the String "hello" as its value.
d["Godot"] = 3.01 # Add String "Godot" as a key and assign the value 3.01 to it.

var test = 4
# Prints "hello" by indexing the dictionary with a dynamic key.
# This is not the same as `d.test`. The bracket syntax equivalent to
# `d.test` is `d["test"]`.
print(d[test])

Nota

Si può utilizzare la sintassi tra parentesi quadre per accedere alle proprietà di qualsiasi Object, non solo dei dizionari. Tenete presente che ciò causerà un errore di script quando si tenta di indicizzare una proprietà inesistente. Per evitare ciò, utilizzare i metodi Object.get() e Object.set().

Dizionari tipizzati

Godot 4.4 ha aggiunto il supporto per i dizionari tipizzati. Durante le operazioni di scrittura, Godot verifica che i valori delle chiavi e dei valori corrispondano al tipo specificato, in modo che il dizionario non contenga chiavi e valori non validi. L'analizzatore statico di GDScript tiene conto dei dizionari tipizzati. Tuttavia, metodi di dizionari restituiranno comunque il tipo Variant.

I dizionari tipizzati hanno la sintassi Dictionary[KeyType, ValueType], dove KeyType e ValueType possono essere qualsiasi tipo Variant, classe nativa o utente, o enumerazione. È necessario specificare sia il tipo della chiave sia il tipo del valore, ma è possibile utilizzare Variant per renderli non tipizzati. Le raccolte tipizzate annidate (come Dictionary[String, Dictionary[String, int]]) non sono supportate.

var a: Dictionary[String, int]
var b: Dictionary[String, Node]
var c: Dictionary[Vector2i, MyClass]
var d: Dictionary[MyEnum, float]
# String keys, values can be any type.
var e: Dictionary[String, Variant]
# Keys can be any type, boolean values.
var f: Dictionary[Variant, bool]

Dictionary e Dictionary[Variant, Variant] sono la stessa cosa.

Signal

Un segnale è un messaggio che può essere emesso da un oggetto a chi desidera ascoltarlo. Il tipo Signal può essere utilizzato per passare l'emettitore in giro.

I segnali sono meglio utilizzati ricavandoli da oggetti reali, ad esempio $Button.button_up.

Callable

Un chiamabile contiene un oggetto e una funzione, il che utile per passare funzioni come fossero valori (ad esempio per la connessione di segnali).

Ottenere un metodo come membro restituisce un chiamabile. var x = $Sprite2D.rotate imposterà il valore di x su un chiamabile avente $Sprite2D come oggetto e rotate come metodo.

È possibile richiamarlo tramite il metodo call: x.call(PI).

Variabili

Le variabili possono esistere come membri di classe o locali alle funzioni. Sono create con la parola chiave var e può, facoltativamente, essere loro assegnato un valore al momento dell'inizializzazione.

var a # Data type is 'null' by default.
var b = 5
var c = 3.8
var d = b + c # Variables are always initialized in direct order (see below).

Le variabili possono facoltativamente avere una specificazione di tipo. Quando si specifica un tipo, la variabile sarà obbligata ad avere sempre lo stesso tipo, e tentando di assegnare un valore incompatibile verrà genererà un errore.

I tipi vengono specificati nella dichiarazione delle variabili utilizzando il simbolo : (due punti) dopo il nome della variabile, seguito dal tipo.

var my_vector2: Vector2
var my_node: Node = Sprite2D.new()

Se la variabile viene inizializzata all'interno della dichiarazione, è possibile dedurne il tipo ed è quindi possibile omettere il nome del tipo:

var my_vector2 := Vector2() # 'my_vector2' is of type 'Vector2'.
var my_node := Sprite2D.new() # 'my_node' is of type 'Sprite2D'.

La deduzione del tipo è possibile solo se il valore assegnato ha un tipo definito, altrimenti verrà generato un errore.

I tipi validi sono:

  • Tipi integrati (Array, Vector2, int, String, ecc.).

  • Classi del motore (Node, Resource, RefCounted, ecc.).

  • Nomi di costanti se contengono una risorsa di script (MyScript se è stato dichiarato const MyScript = preload("res://my_script.gd")).

  • Altre classi nello stesso script, rispettando l'ambito (InnerClass.NestedClass se è stato dichiarato class NestedClass all'interno di class InnerClass nello stesso ambito).

  • Classi di script dichiarate con la parola chiave class_name.

  • Autoload registrati come singleton.

Nota

Sebbene Variant sia una specificazione di tipo valida, non è un tipo vero e proprio. Significa solo che non esiste un tipo impostato ed è equivalente a non avere affatto un tipo statico. Pertanto, solitamente, dedurre il tipo come Variant non è consentito, poiché è probabile che sia un sbaglio.

È possibile disattivare questa verifica, o renderla solo un avviso, modificandola nelle impostazioni del progetto. Consultare Sistema di avvertimento in GDScript per i dettagli.

Ordine di inizializzazione

Le variabili membro vengono inizializzate nel seguente ordine:

  1. A seconda del tipo statico della variabile, la variabile è null (variabili non tipizzate e oggetti) oppure ha un valore predefinito del tipo (0 per int, false per bool, ecc.).

  2. I valori specificati vengono assegnati nell'ordine delle variabili nello script, dall'alto verso il basso.

    • (Solamente per le classi derivate da Node) Se l'annotazione @onready viene applicata a una variabile, la sua inizializzazione viene posticipata al passaggio 5.

  3. Se definito, il metodo _init() viene chiamato.

  4. Per l'istanziazione di istanze di scene e di risorse, i valori esportati vengono assegnati.

  5. (Solamente per le classi derivate da Node) Le variabili @onready vengono inizializzate.

  6. (Solamente per le classi derivate da Node) Se definito, il metodo _ready() viene chiamato.

Avvertimento

È possibile specificare un'espressione complessa per inizializzare una variabile, incluse chiamate di funzioni. Assicurarsi che le variabili siano inizializzate nell'ordine corretto, altrimenti i loro valori potrebbero essere sovrascritti. Ad esempio:

var a: int = proxy("a", 1)
var b: int = proxy("b", 2)
var _data: Dictionary = {}

func proxy(key: String, value: int):
    _data[key] = value
    print(_data)
    return value

func _init() -> void:
    print(_data)

Stamperà:

{ "a": 1 }
{ "a": 1, "b": 2 }
{  }

Per risolvere questo problema, spostare la definizione della variabile _data sopra la definizione di a o rimuovere l'assegnazione del dizionario vuoto (= {}).

Variabili statiche

Una variabile membro di una classe può essere dichiarata statica:

static var a

Le variabili statiche appartengono alla classe, non alle istanze. Ciò significa che le variabili statiche condividono i valori tra più istanze, a differenza delle normali variabili membro.

Dall'interno di una classe, è possibile accedere alle variabili statiche da qualsiasi funzione, sia statica sia non statica. Dall'esterno della classe, è possibile accedere alle variabili statiche attraverso la classe stessa o un'istanza (la seconda opzione è sconsigliata in quanto meno leggibile).

Nota

Le annotazioni @export e @onready non possono essere applicate a una variabile statica. Le variabili locali non possono essere statiche.

L'esempio seguente definisce una classe Person con una variabile statica con il nome max_id. max_id viene incrementato nella funzione _init(). Facendo ciò, è più facile monitorare il numero di istanze di Person nel gioco.

# person.gd
class_name Person

static var max_id = 0

var id
var name

func _init(p_name):
    max_id += 1
    id = max_id
    name = p_name

In questo codice, si creano due istanze della nostra classe Person e si verifica che la classe e ogni istanza abbiano lo stesso valore di max_id, perché la variabile è statica e accessibile a ogni istanza.

# test.gd
extends Node

func _ready():
    var person1 = Person.new("John Doe")
    var person2 = Person.new("Jane Doe")

    print(person1.id) # 1
    print(person2.id) # 2

    print(Person.max_id)  # 2
    print(person1.max_id) # 2
    print(person2.max_id) # 2

Le variabili statiche possono avere suggerimenti sul tipo, setter e getter:

static var balance: int = 0

static var debt: int:
    get:
        return -balance
    set(value):
        balance = -value

È possibile accedere a una variabile statica della classe padre anche tramite una classe figlia:

class A:
    static var x = 1

class B extends A:
    pass

func _ready():
    prints(A.x, B.x) # 1 1
    A.x = 2
    prints(A.x, B.x) # 2 2
    B.x = 3
    prints(A.x, B.x) # 3 3

Nota

Quando si fa riferimento a una variabile statica da uno script strumento (tool), anche l'altro script contenente la variabile statica deve essere uno script strumento. Consultare Esecuzione del codice nell'editor per maggiori dettagli.

Annotazione @static_unload

Poiché le classi GDScript sono risorse, la presenza di variabili statiche in uno script ne impedisce lo scaricamento anche se non ci sono più istanze di quella classe e non ci sono altri riferimenti. Questo può essere importante se le variabili statiche memorizzano grandi quantità di dati o contengono riferimenti ad altre risorse del progetto, come le scene. È consigliabile pulire questi dati manualmente o utilizzare l'annotazione @static_unload se le variabili statiche non memorizzano dati importanti e si possono ripristinare.

Avvertimento

Attualmente, a causa di un bug, gli script non vengono mai liberati, anche se viene utilizzata l'annotazione @static_unload.

Si noti che @static_unload si applica all'intero script (incluse le classi interne) e deve essere posizionato all'inizio dello script, prima di class_name e extends:

@static_unload
class_name MyNode
extends Node

Consultare anche Static functions e Static constructor.

Conversione del tipo

I valori assegnati alle variabili tipizzate devono essere di tipo compatibile. Se è necessario costringere un valore a essere di un certo tipo, in particolare per i tipi di oggetti, è possibile utilizzare l'operatore di conversione as (casting).

La conversione tra tipi di oggetti produce lo stesso oggetto se il valore è dello stesso tipo o di un sottotipo del tipo di conversione.

var my_node2D: Node2D
my_node2D = $Sprite2D as Node2D # Works since Sprite2D is a subtype of Node2D.

Se il valore non è un sottotipo, l'operazione di conversione darà come risultato un valore null.

var my_node2D: Node2D
my_node2D = $Button as Node2D # Results in 'null' since a Button is not a subtype of Node2D.

Per i tipi integrati, se possibile, la conversione verrà forzata, altrimenti il motore genererà un errore.

var my_int: int
my_int = "123" as int # The string can be converted to int.
my_int = Vector2() as int # A Vector2 can't be converted to int, this will cause an error.

La conversione è utile anche per avere variabili più sicure quando si interagisce con l'albero di scene:

# Will infer the variable to be of type Sprite2D.
var my_sprite := $Character as Sprite2D

# Will fail if $AnimPlayer is not an AnimationPlayer, even if it has the method 'play()'.
($AnimPlayer as AnimationPlayer).play("walk")

Costanti

Le costanti sono valori che non è possibile modificare durante l'esecuzione del gioco. Il loro valore deve essere noto in fase di compilazione. L'utilizzo della parola chiave const consente di assegnare un nome a un valore costante. Tentare di assegnare un valore a una costante dopo che è stata dichiarata genererà un errore.

Si consiglia di utilizzare le costanti ogni volta che non si prevede che un valore cambi.

const A = 5
const B = Vector2(20, 20)
const C = 10 + 20 # Constant expression.
const D = Vector2(20, 30).x # Constant expression: 20.
const E = [1, 2, 3, 4][0] # Constant expression: 1.
const F = sin(20) # 'sin()' can be used in constant expressions.
const G = x + 20 # Invalid; this is not a constant expression!
const H = A + 20 # Constant expression: 25 (`A` is a constant).

Sebbene il tipo delle costanti sia dedotto dal valore assegnato, è anche possibile aggiungere una specificazione esplicita del tipo:

const A: int = 5
const B: Vector2 = Vector2()

Assegnare un valore di un tipo incompatibile genererà un errore.

È anche possibile creare costanti all'interno di una funzione, il che è utile per assegnare un nome ai valori magici locali.

Enumerazioni

Le enumerazioni sono basicamente una forma abbreviata per le costanti, e sono molto utili se si desidera assegnare numeri interi consecutivi a certe costanti.

enum {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}

# Is the same as:
const TILE_BRICK = 0
const TILE_FLOOR = 1
const TILE_SPIKE = 2
const TILE_TELEPORT = 3

Se si passa un nome all'enumerazione, tutte le chiavi saranno inserite in un Dictionary costante con quel nome. Ciò significa che tutti i metodi costanti di un dizionario si possono utilizzare anche con un'enumerazione con nome. Questo funziona solo per le enumerazioni in GDScript, non per quelle delle classi integrate.

Importante

Le chiavi in un enumerazione con nome non vengono registrate come costanti globali. Il loro accesso deve essere effettuato anteponendo il nome dell'enumerazione (Name.KEY).

enum State {STATE_IDLE, STATE_JUMP = 5, STATE_SHOOT}

# Is the same as:
const State = {STATE_IDLE = 0, STATE_JUMP = 5, STATE_SHOOT = 6}
# Access values with State.STATE_IDLE, etc.

func _ready():
    # Access values with Name.KEY, prints '5'
    print(State.STATE_JUMP)
    # Use dictionary methods:
    # prints '["STATE_IDLE", "STATE_JUMP", "STATE_SHOOT"]'
    print(State.keys())
    # prints '{ "STATE_IDLE": 0, "STATE_JUMP": 5, "STATE_SHOOT": 6 }'
    print(State)
    # prints '[0, 5, 6]'
    print(State.values())

Se non si assegna un valore a una chiave di un enumerazione, le verrà assegnato il valore precedente più uno, oppure 0 se è la prima voce dell'enumerazione. Sono consentite più chiavi con lo stesso valore.

Funzioni

Le funzioni appartengono sempre a una classe. La priorità di ambito per la ricerca delle variabili è: locale → membro della classe → globale. La variabile self è sempre disponibile ed è fornita come opzione per accedere ai membri della classe (vedi self), ma non è sempre necessaria (e non dovrebbe essere inviata come primo argomento della funzione, diversamente da Python).

func my_function(a, b):
    print(a)
    print(b)
    return a + b  # Return is optional; without it 'null' is returned.

Una funzione può restituire, tramite la parola chiave return, in qualsiasi punto. Il valore restituito predefinito è null.

Normalmente, tutti i parametri di una funzione sono obbligatori. È possibile rendere facoltativi uno o più parametri alla fine, assegnando loro un valore predefinito:

# Since the last two parameters are optional, all these calls are valid:
# - my_function(1)
# - my_function(1, 20)
# - my_function(1, 20, 100)
func my_function(a_required, b_optional = 10, c_optional = 42):
    print(a_required)
    print(b_optional)
    print(c_optional)

Se una funzione contiene una sola riga di codice, la si può scrivere su una sola riga:

func square(a): return a * a

func hello_world(): print("Hello World")

func empty_function(): pass

Le funzioni possono anche avere specificazioni di tipo per gli argomenti e per il valore restituito. I tipi per gli argomenti si possono aggiungere in modo simile alle variabili:

func my_function(a: int, b: String):
    pass

Se l'argomento di una funzione ha un valore predefinito, è possibile dedurne il tipo:

func my_function(int_arg := 42, String_arg := "string"):
    pass

Il tipo restituito della funzione si può specificare dopo l'elenco degli argomenti utilizzando il token di freccia (->):

func my_int_function() -> int:
    return 0

Le funzioni che hanno un tipo devono restituire un valore appropriato. Impostando il tipo come void, la funzione non restituisce nulla. Le funzioni void possono restituire in anticipo con la parola chiave return, ma non possono restituire alcun valore.

func void_function() -> void:
    return # Can't return a value.

Nota

Le funzioni non-void devono sempre restituire un valore, quindi se il codice contiene istruzioni di diramazione (come un costrutto if/else), tutti i possibili percorsi devono avere un ritorno. Ad esempio, se si ha un return all'interno di un blocco if ma non dopo, l'editor genererà un errore perché, se il blocco non viene eseguito, la funzione non avrà un valore valido da restituire.

Funzioni di riferimento

Le funzioni sono valori di prima classe in termini dell'oggetto Callable. Fare riferimento a una funzione per nome senza chiamarla genererà automaticamente il chiamabile corrispondente. Ciò si può utilizzare per passare le funzioni come argomenti.

func map(arr: Array, function: Callable) -> Array:
    var result = []
    for item in arr:
        result.push_back(function.call(item))
    return result

func add1(value: int) -> int:
    return value + 1;

func _ready() -> void:
    var my_array = [1, 2, 3]
    var plus_one = map(my_array, add1)
    print(plus_one) # Prints `[2, 3, 4]`.

Nota

I chiamabili devono essere chiamati con il metodo call(). Non è possibile utilizzare direttamente l'operatore (). Questo comportamento è implementato per evitare problemi di prestazioni nelle chiamate dirette di funzioni.

Funzioni lambda

Le funzioni lambda consentono di dichiarare funzioni che non appartengono a una classe. Invece, viene creato un oggetto Callable e assegnato direttamente a una variabile. Ciò può essere utile per creare chiamabili da passare in giro senza inquinare l'ambito della classe.

var lambda = func (x):
    print(x)

Per chiamare la lambda creata è possibile usare il metodo call():

lambda.call(42) # Prints `42`.

È possibile assegnare un nome alle funzioni lambda, per motivi di debug (il nome è visualizzato nel Debugger):

var lambda = func my_lambda(x):
    print(x)

È possibile specificare suggerimenti del tipo per le funzioni lambda nello stesso modo in cui si fa per quelle normali:

var lambda := func (x: int) -> void:
    print(x)

Tenere presente che se si desidera restituire un valore da una funzione lambda, è necessario un return esplicito (non si può omettere return):

var lambda = func (x): return x ** 2
print(lambda.call(2)) # Prints `4`.

Le funzioni lambda catturano l'ambiente locale:

var x = 42
var lambda = func ():
    print(x) # Prints `42`.
lambda.call()

Avvertimento

Le variabili locali vengono catturate per valore una sola volta, quando viene creata la lambda. Pertanto, non verranno aggiornate all'interno della lambda se riassegnate nella funzione esterna:

var x = 42
var lambda = func (): print(x)
lambda.call() # Prints `42`.
x = "Hello"
lambda.call() # Prints `42`.

Inoltre, una lambda non può riassegnare una variabile locale esterna. Dopo l'uscita dalla lambda, la variabile rimarrà invariata, perché la cattura nella lambda la oscura implicitamente:

var x = 42
var lambda = func ():
    print(x) # Prints `42`.
    x = "Hello" # Produces the `CONFUSABLE_CAPTURE_REASSIGNMENT` warning.
    print(x) # Prints `Hello`.
lambda.call()
print(x) # Prints `42`.

Tuttavia, se si usano tipi di dati passati per riferimento (array, dizionari e oggetti), le modifiche al loro contenuto sono condivise finché la variabile non viene riassegnata:

var a = []
var lambda = func ():
    a.append(1)
    print(a) # Prints `[1]`.
    a = [2] # Produces the `CONFUSABLE_CAPTURE_REASSIGNMENT` warning.
    print(a) # Prints `[2]`.
lambda.call()
print(a) # Prints `[1]`.

Funzioni statiche

Una funzione può essere dichiarata statica. Quando una funzione è statica, non ha accesso alle variabili membro di un'istanza o a self. Una funzione statica ha accesso a variabili statiche. Le funzioni statiche sono utili anche per creare librerie di funzioni ausiliari:

static func sum2(a, b):
    return a + b

Le funzioni lambda non possono essere dichiarate statiche.

Consultare anche Static variables e Static constructor.

Funzioni variadiche

Una funzione variadica è una funzione che può accettare un numero variabile di argomenti. A partire da Godot 4.5, GDScript supporta le funzioni variadiche. Per dichiarare una funzione variadica, è necessario utilizzare il parametro rest, che raccoglie tutti gli argomenti in eccesso in un array.

func my_func(a, b = 0, ...args):
    prints(a, b, args)

func _ready():
    my_func(1)             # 1 0 []
    my_func(1, 2)          # 1 2 []
    my_func(1, 2, 3)       # 1 2 [3]
    my_func(1, 2, 3, 4)    # 1 2 [3, 4]
    my_func(1, 2, 3, 4, 5) # 1 2 [3, 4, 5]

Una funzione può avere al massimo un solo parametro rest, che deve essere l'ultimo nell'elenco dei parametri. Il parametro rest non può avere un valore predefinito. Anche le funzioni statiche e lambda possono essere variadiche.

La tipizzazione statica funziona anche per le funzioni variadiche. Tuttavia, gli array tipizzati non sono attualmente supportati come tipo statico del parametro rest:

# You cannot specify `...values: Array[int]`.
func sum(...values: Array) -> int:
    var result := 0
    for value in values:
        assert(value is int)
        result += value
    return result

Nota

Sebbene sia possibile dichiarare funzioni come variadiche utilizzando il parametro rest, in GDScript non è attualmente supportata l'estrazione dei parametri quando si chiama una funzione attraverso la sintassi spread presente in alcuni linguaggi (JavaScript, PHP). Tuttavia, è possibile utilizzare callv() per chiamare una funzione con un array di argomenti:

func test_func(...args):
    #log_data(...args) # This won't work.
    log_data.callv(args) # This will work.

func log_data(...values):
    # You should use `callv()` if you want to pass `values` as the argument list,
    # rather than passing the array as the first argument.
    prints.callv(values)
    # You can use array concatenation to prepend/append the argument list.
    write_data.callv(["user://log.txt"] + values)

func write_data(path, ...values):
    # ...

Funzioni astratte

Consultare Classi e metodi astratti.

Dichiarazioni e flusso di controllo

Le istruzioni sono standard e possono essere assegnamenti, chiamate di funzioni, strutture di flusso di controllo, ecc. (vedere sotto). Utilizzare ; come separatore tra le istruzioni è completamente facoltativo.

Espressioni

Le espressioni sono sequenze ordinate di operatori e dei loro operandi. Un'espressione di per sé può anche essere un'istruzione, sebbene sia ragionevole utilizzare come istruzioni solo le chiamate, poiché altre espressioni non hanno effetti collaterali.

Le espressioni restituiscono valori che si possono assegnare a obiettivi validi. Gli operandi di un operatore possono essere un'altra espressione. Un'assegnazione non è un'espressione e quindi non restituisce alcun valore.

Ecco alcuni esempi di espressioni:

2 + 2 # Binary operation.
-5 # Unary operation.
"okay" if x > 4 else "not okay" # Ternary operation.
x # Identifier representing variable or constant.
x.a # Attribute access.
x[4] # Subscript access.
x > 2 or x < 5 # Comparisons and logic operators.
x == y + 2 # Equality test.
do_something() # Function call.
[1, 2, 3] # Array definition.
{A = 1, B = 2} # Dictionary definition.
preload("res://icon.svg") # Preload builtin function.
self # Reference to current instance.

Gli identificatori, gli attributi e gli indici sono obiettivi di assegnazione validi. Altre espressioni non si possono inserire sul lato sinistro di un'assegnazione.

self

È possibile utilizzare self per riferirsi all'istanza attuale ed è spesso equivalente a fare riferimento diretto ai simboli disponibili nello script attuale. Tuttavia, self consente anche di accedere a proprietà, metodi e altri nomi definiti dinamicamente (ovvero che si prevede esistano nei sottotipi della classe attuale o che vengono forniti tramite _set() e/o _get()).

extends Node

func _ready():
    # Compile time error, as `my_var` is not defined in the current class or its ancestors.
    print(my_var)
    # Checked at runtime, thus may work for dynamic properties or descendant classes.
    print(self.my_var)

    # Compile time error, as `my_func()` is not defined in the current class or its ancestors.
    my_func()
    # Checked at runtime, thus may work for descendant classes.
    self.my_func()

Avvertimento

Attenzione che accedere ai membri delle classi figlie nella classe padre è spesso considerato una cattiva pratica, poiché confonde l'area di responsabilità di qualsiasi pezzo di codice, rendendo più difficile ragionare su tutte le correlazioni tra le parti del gioco. Oltre a ciò, ci si può semplicemente dimenticare che la classe padre aveva aspettative sui suoi discendenti.

if/else/elif

Le condizioni semplici si creano utilizzando la sintassi if/else/elif. Si permette l'uso di parentesi intorno alle condizioni, ma non è obbligatorio. Data la natura dell'indentazione basata sulla tabulazione, è possibile usare elif al posto di else/if per mantenere un livello di indentazione.

if (expression):
    statement(s)
elif (expression):
    statement(s)
else:
    statement(s)

Le istruzioni brevi si possono scrivere sulla stessa riga della condizione:

if 1 + 1 == 2: return 2 + 2
else:
    var x = 3 + 3
    return x

A volte, potrebbe essere necessario assegnare un valore iniziale diverso in base a un'espressione booleana. In questi casi, le espressioni if ternarie possono rivelarsi utili:

var x = (value) if (expression) else (value)
y += 3 if y < 10 else -1

Le espressioni if ternarie si possono annidare per gestire più di 2 casi. Quando si annidano espressioni if ternarie, si consiglia di suddividere l'espressione completa su più righe per preservarne la leggibilità:

var count = 0

var fruit = (
        "apple" if count == 2
        else "pear" if count == 1
        else "banana" if count == 0
        else "orange"
)
print(fruit)  # banana

# Alternative syntax with backslashes instead of parentheses (for multi-line expressions).
# Less lines required, but harder to refactor.
var fruit_alt = \
        "apple" if count == 2 \
        else "pear" if count == 1 \
        else "banana" if count == 0 \
        else "orange"
print(fruit_alt)  # banana

Potrebbe anche essere necessario verificare se un valore è contenuto in qualcosa. Per farlo, si può utilizzare un'istruzione if combinata con l'operatore in:

# Check if a letter is in a string.
var text = "abc"
if 'b' in text: print("The string contains b")

# Check if a variable is contained within a node.
if "varName" in get_parent(): print("varName is defined in parent!")

while

I cicli semplici si creano utilizzando la sintassi while. I cicli si possono interrompere tramite break o continuare tramite continue (che passa all'iterazione successiva del ciclo senza eseguire ulteriore codice nell'iterazione in corso):

while (expression):
    statement(s)

for

Per iterare su un intervallo, come un array o una tabella, si utilizza un ciclo for. Quando si itera su un array, l'elemento attuale dell'array viene memorizzato nella variabile del ciclo. Quando si itera su un dizionario, la chiave viene memorizzata nella variabile del ciclo.

for x in [5, 7, 11]:
    statement # Loop iterates 3 times with 'x' as 5, then 7 and finally 11.

var names = ["John", "Marta", "Samantha", "Jimmy"]
for name: String in names: # Typed loop variable.
    print(name) # Prints name's content.

var dict = {"a": 0, "b": 1, "c": 2}
for i in dict:
    print(dict[i]) # Prints 0, then 1, then 2.

for i in range(3):
    statement # Similar to [0, 1, 2] but does not allocate an array.

for i in range(1, 3):
    statement # Similar to [1, 2] but does not allocate an array.

for i in range(2, 8, 2):
    statement # Similar to [2, 4, 6] but does not allocate an array.

for i in range(8, 2, -2):
    statement # Similar to [8, 6, 4] but does not allocate an array.

for c in "Hello":
    print(c) # Iterate through all characters in a String, print every letter on new line.

for i in 3:
    statement # Similar to range(3).

for i in 2.2:
    statement # Similar to range(ceil(2.2)).

Per assegnare valori a un array durante la sua iterazione, è meglio utilizzare for i in array.size().

for i in array.size():
    array[i] = "Hello World"

La variabile del ciclo è locale al ciclo for e assegnarla non modifica il valore dell'array. Gli oggetti passati per riferimento (come i nodi) si possono comunque manipolare chiamando metodi sulla variabile del ciclo.

for string in string_array:
    string = "Hello World" # This has no effect

for node in node_array:
    node.add_to_group("Cool_Group") # This has an effect

match

Un'istruzione match serve per diramare l'esecuzione di un programma. È l'equivalente dell'istruzione switch presente in molti altri linguaggi, ma offre alcune funzionalità in più.

Avvertimento

match è più restrittivo per il tipo rispetto all'operatore ==. Ad esempio, 1 non corrisponderà a 1.0. L'unica eccezione è la corrispondenza tra String e StringName: ad esempio, la stringa "hello" è considerata uguale allo StringName &"hello".

Sintassi di base

match <test value>:
    <pattern(s)>:
        <block>
    <pattern(s)> when <pattern guard>:
        <block>
    <...>

Corso accelerato per chi ha familiarità con le istruzioni switch

  1. Sostituire switch con match.

  2. Rimuovere case.

  3. Rimuovere eventuali break.

  4. Sostituire default con un singolo trattino basso (_).

Flusso di controllo

Gli schemi sono confrontati dall'alto verso il basso. Se uno schema corrisponde, verrà eseguito il primo blocco corrispondente. Successivamente, l'esecuzione continua sotto l'istruzione match.

Nota

Il comportamento speciale di continue in match supportato nella versione 3.x è stato rimosso in Godot 4.0.

Sono disponibili i seguenti tipi di schema:

  • Schema letterale

    Corrisponde a un letterale:

    match x:
        1:
            print("We are number one!")
        2:
            print("Two are better than one!")
        "test":
            print("Oh snap! It's a string!")
    
  • Schema di espressione

    Corrisponde a un'espressione costante, a un identificatore o a un accesso a un attributo (A.B):

    match typeof(x):
        TYPE_FLOAT:
            print("float")
        TYPE_STRING:
            print("text")
        TYPE_ARRAY:
            print("array")
    
  • Schema jolly (wildcard)

    Questo schema corrisponde a tutto. È scritto come un singolo trattino basso (_).

    Può essere utilizzato come equivalente di default in un'istruzione switch in altri linguaggi:

    match x:
        1:
            print("It's one!")
        2:
            print("It's one times two!")
        _:
            print("It's not 1 or 2. I don't care to be honest.")
    
  • Schema vincolante

    Un schema vincolante introduce una nuova variabile. Come lo schema jolly, corrisponde a tutto e assegna anche un nome al valore corrispondente. È particolarmente utile negli schemi di array e di dizionari:

    match x:
        1:
            print("It's one!")
        2:
            print("It's one times two!")
        var new_var:
            print("It's not 1 or 2, it's ", new_var)
    
  • Schema di array

    Corrisponde a un array. Ogni singolo elemento dello schema di array è a sua volta uno schema, quindi è possibile annidarli.

    Viene prima testata la dimensione dell'array: deve essere della stessa dimensione dello schema, altrimenti lo schema non corrisponde.

    Array aperto: un array può essere più grande dello schema impostando l'ultimo sotto-schema su ...

    Ogni sotto-schema deve essere separato da virgole.

    match x:
        []:
            print("Empty array")
        [1, 3, "test", null]:
            print("Very specific array")
        [var start, _, "test"]:
            print("First element is ", start, ", and the last is \"test\"")
        [42, ..]:
            print("Open ended array")
    
  • Schema di dizionario

    Funziona allo stesso modo dello schema di array. Ogni chiave deve essere uno schema costante.

    Viene prima testata la dimensione del dizionario: deve essere della stessa dimensione dello schema, altrimenti lo schema non corrisponde.

    Dizionario aperto: un dizionario può essere più grande dello schema impostando l'ultimo sotto-schema su ...

    Ogni sotto-schema deve essere separato da una virgola.

    Se non si specifica un valore, viene verificata solo l'esistenza della chiave.

    Uno schema di valore è separato dallo schema di chiave con un :.

    match x:
        {}:
            print("Empty dict")
        {"name": "Dennis"}:
            print("The name is Dennis")
        {"name": "Dennis", "age": var age}:
            print("Dennis is ", age, " years old.")
        {"name", "age"}:
            print("Has a name and an age, but it's not Dennis :(")
        {"key": "godotisawesome", ..}:
            print("I only checked for one entry and ignored the rest")
    
  • Più di uno schema

    È anche possibile specificare più schemi separati da una virgola. Questi schemi non possono contenere vincoli.

    match x:
        1, 2, 3:
            print("It's 1 - 3")
        "Sword", "Splash potion", "Fist":
            print("Yep, you've taken damage")
    

Guardie di schema

Una guardia di schema è una condizione facoltativa che segue l'elenco degli schemi e consente di effettuare verifiche aggiuntive prima di scegliere un ramo di match. A differenza di uno schema, una guardia di schema può essere un'espressione arbitraria.

È possibile eseguire un solo ramo per match. Una volta scelto un ramo, quelli rimasti non vengono verificati. Se serve usare lo stesso schema per più rami o per evitare che sia scelto un ramo con uno schema troppo generico, è possibile specificare una guardia di schema dopo l'elenco degli schemi con la parola chiave when:

match point:
    [0, 0]:
        print("Origin")
    [_, 0]:
        print("Point on X-axis")
    [0, _]:
        print("Point on Y-axis")
    [var x, var y] when y == x:
        print("Point on line y = x")
    [var x, var y] when y == -x:
        print("Point on line y = -x")
    [var x, var y]:
        print("Point (%s, %s)" % [x, y])
  • Se non esiste alcuno schema corrispondente per il ramo attuale, la guardia dello schema non viene valutata e vengono verificati gli schemi del ramo successivo.

  • Se viene trovato uno schema corrispondente, la guardia dello schema viene valutata.

    • Se è vera, allora il corpo del ramo viene eseguito e match termina.

    • Se è falsa, allora vengono verificati gli schemi del ramo successivo.

Classi

Come predefinito, tutti i file di script sono classi senza nome. In questo caso, vi si può solo far riferimento tramite il percorso del file, usando un percorso relativo o assoluto. Ad esempio, se un file di script è denominato character.gd:

# Inherit from 'character.gd'.

extends "res://path/to/character.gd"

# Load character.gd and create a new node instance from it.

var Character = load("res://path/to/character.gd")
var character_node = Character.new()

Registrazione di classi con nome

È possibile assegnare un nome alla propria classe per registrarla come nuovo tipo nell'editor di Godot. Lo si fa tramite la parola chiave class_name. È possibile facoltativamente usare l'annotazione @icon avente un percorso a un'immagine, per utilizzare tale come icona. La classe apparirà quindi con la sua nuova icona nell'editor:

# item.gd

@icon("res://interface/icons/item.png")
class_name Item
extends Node
../../../_images/class_name_editor_register_example.png

Suggerimento

Le immagini SVG utilizzate come icone personalizzate di nodi devono avere le opzioni di importazione Editor > Scala con la scala dell'editor e Editor > Converti le icone con il tema dell'editor abilitate. Ciò consente alle icone di seguire la scala dell'editor e le impostazioni del tema, se esse sono progettate con la stessa tavolozza di colori usata dalle icone di Godot.

Ecco un esempio di file di classe:

# Saved as a file named 'character.gd'.

class_name Character


var health = 5


func print_health():
    print(health)


func print_this_script_three_times():
    print(get_script())
    print(ResourceLoader.load("res://character.gd"))
    print(Character)

Se si desidera usare anche extends, si possono mantenere entrambi sulla stessa riga:

class_name MyNode extends Node

Le classi denominate sono registrate globalmente, il che significa che diventano disponibili per l'uso in altri script senza aver bisogno di caricarle (load) o precaricarle (preload):

var player

func _ready():
    player = Character.new()

Nota

Godot inizializza le variabili non statiche ogni volta che viene creata un'istanza, inclusi array e dizionari. Questo è in linea con lo spirito di sicurezza dei thread, poiché gli script si possono inizializzare in thread separati senza che l'utente lo sappia.

Avvertimento

L'editor Godot nasconderà queste classi personalizzate i cui nomi iniziano con il prefisso "Editor" nelle finestre di dialogo "Crea un nuovo nodo" o "Crea una nuova scena". Le classi si possono istanziare tramite i rispettivi nomi in fase di esecuzione, ma sono automaticamente nascoste dalle finestre dell'editor insieme ai nodi integrati specifici dell'editor Godot.

Classi e metodi astratti

A partire da Godot 4.5, è possibile definire classi e metodi astratti usando l'annotazione @abstract.

Una classe astratta è una classe che non si può istanziare direttamente. È invece pensata per essere ereditata da altre classi. Tentare di istanziare una classe astratta genererà un errore.

Un metodo astratto è un metodo che non ha un'implementazione. Pertanto, è prevista una nuova riga o un punto e virgola dopo l'intestazione della funzione. Questo definisce un contratto a cui le classi ereditarie devono conformarsi, poiché la firma del metodo deve essere compatibile se ridefinito.

Le classi ereditarie devono fornire implementazioni per tutti i metodi astratti, oppure la classe ereditaria deve essere segnata come astratta. Se una classe ha almeno un metodo astratto (che sia il proprio, o ereditato ma non implementato), allora anche essa deve essere segnata come astratta. Tuttavia, il contrario non è vero: una classe astratta può non avere metodi astratti.

Suggerimento

Se si desidera dichiarare un metodo come facoltativo da sovrascrivere, è necessario utilizzare un metodo non astratto e fornire un'implementazione predefinita.

Ad esempio, si potrebbe avere una classe astratta chiamata Shape che definisce un metodo astratto chiamato draw(). Si possono quindi creare sottoclassi come Circle e Square che implementano il metodo draw() a modo loro. Questo permette di definire un'interfaccia comune per tutte le forme senza dover implementare tutti i dettagli nella classe astratta stessa:

@abstract class Shape:
    @abstract func draw()

# This is a concrete (non-abstract) subclass of Shape.
# You **must** implement all abstract methods in concrete classes.
class Circle extends Shape:
    func draw():
        print("Drawing a circle.")

class Square extends Shape:
    func draw():
        print("Drawing a square.")

Sia le classi interne sia quelle create utilizzando class_name possono essere astratte. Questo esempio crea due classi astratte, una delle quali è una sottoclasse di un'altra classe astratta:

@abstract
class_name AbstractClass
extends Node

@abstract class AbstractInnerClass:
    func _ready():
        pass

# This is an example of a concrete subclass of `AbstractInnerClass`.
# This class can be instantiated using `AbstractClass.ConcreteInnerClass.new()`
# in other scripts, even though it's part of an abstract `class_name` script.
class ConcreteInnerClass extends AbstractInnerClass:
    func _ready():
        print("Concrete class ready.")

Avvertimento

Poiché una classe astratta non può essere istanziata, non è possibile allegare una classe astratta a un nodo. Se si tenta di farlo, il motore genererà un errore durante l'esecuzione della scena:

Cannot set object script. Script '<path to script>' should not be abstract.

Anche le classi senza nome possono essere astratte, l'annotazione @abstract deve precedere extends:

@abstract
extends Node

Ereditarietà

Una classe (memorizzata come file) può ereditare da:

  • Una classe globale.

  • Un altro file di classe.

  • Una classe interna all'interno di un altro file di classe.

L'ereditarietà multipla non è consentita.

L'ereditarietà utilizza la parola chiave extends:

# Inherit/extend a globally available class.
extends SomeClass

# Inherit/extend a named class file.
extends "somefile.gd"

# Inherit/extend an inner class in another file.
extends "somefile.gd".SomeInnerClass

Nota

Se l'ereditarietà non è definita esplicitamente, la classe erediterà come predefinito RefCounted.

Per verificare se un'istanza eredita da una determinata classe, è possibile usare la parola chiave is:

# Cache the enemy class.
const Enemy = preload("enemy.gd")

# [...]

# Use 'is' to check inheritance.
if entity is Enemy:
    entity.apply_damage()

Per chiamare una funzione in una classe base (vale a dire la classe estesa con extend nella classe attuale), usare la parola chiave super:

super(args)

Ciò è particolarmente utile perché le funzioni nelle classi estese sovrascrivono le funzioni con lo stesso nome nelle loro classi padre. Se si desidera chiamarle comunque, è possibile usare super:

func some_func(x):
    super(x) # Calls the same function on the super class.

Se è necessario chiamare una funzione differente dalla classe padre, è possibile specificare il nome della funzione con l'operatore di attributo:

func overriding():
    return 0 # This overrides the method in the base class.

func dont_override():
    return super.overriding() # This calls the method as defined in the base class.

Avvertimento

Uno dei malintesi più comuni è cercare di sovrascrivere i metodi non virtuali del motore, come get_class(), queue_free(), ecc. Questo non è supportato per motivi tecnici.

In Godot 3, è possibile oscurare i metodi del motore in GDScript, e funzionerà se questo metodo viene chiamato in GDScript. Tuttavia, il motore non eseguirà il codice se il metodo viene chiamato al suo interno per qualunque motivo.

In Godot 4, l'oscuramento non funziona sempre, poiché GDScript ottimizza le chiamate ai metodi nativi. Pertanto, è stato introdotto l'avviso NATIVE_METHOD_OVERRIDE, il quale è normalmente considerato un errore. Si sconsiglia vivamente di disabilitare o ignorare l'avviso.

Si noti che ciò non si applica ai metodi virtuali come _ready(), _process() e altri (contrassegnati con il qualificatore virtual nella documentazione e i cui nomi iniziano con un trattino basso). Questi metodi servono specificamente per personalizzare il comportamento del motore e possono essere sovrascritti in GDScript. Segnali e notifiche possono anche essere utili a questo scopo.

Costruttore di classe

Il costruttore della classe, chiamato durante l'istanziazione della classe, è denominato _init. Per chiamare il costruttore della classe base, è possibile utilizzare anche la sintassi super. Si noti che ogni classe ha un costruttore implicito che viene sempre chiamato (definendo i valori predefiniti delle variabili della classe). super serve per chiamare il costruttore esplicito:

func _init(arg):
   super("some_default", arg) # Call the custom base constructor.

Questo è meglio spiegato attraverso qualche esempio. Si consideri questo scenario:

# state.gd (inherited class).
var entity = null
var message = null


func _init(e = null):
    entity = e


func enter(m):
    message = m


# idle.gd (inheriting class).
extends "state.gd"


func _init(e = null, m = null):
    super(e)
    # Do something with 'e'.
    message = m

Ci sono alcune cose da tenere a mente qui:

  1. Se la classe ereditata (state.gd) definisce un costruttore _init che accetta argomenti (e in questo caso), allora anche la classe ereditaria (idle.gd) deve definire _init e passare i parametri appropriati a _init da state.gd.

  2. idle.gd può avere un numero diverso di argomenti rispetto alla classe padre state.gd.

  3. Nell'esempio sopra, il parametro e passato al costruttore state.gd è lo stesso parametro e passato a idle.gd.

  4. Se il costruttore _init di idle.gd accetta 0 argomenti, deve comunque passare un valore alla classe base state.gd, anche se non fa nulla. Questo porta al fatto che è possibile passare anche espressioni al costruttore base, non solo variabili, ad esempio:

# idle.gd

func _init():
    super(5)

Costruttore statico

Un costruttore statico è una funzione statica _static_init che viene chiamata automaticamente quando la classe viene caricata, dopo che le variabili statiche sono state inizializzate:

static var my_static_var = 1

static func _static_init():
    my_static_var = 2

Un costruttore statico non può accettare argomenti e non deve restituire alcun valore.

Classi interne

Un file di classe può contenere classi interne. Le classi interne sono definite usando la parola chiave class. Vengono istanziate tramite la funzione ClassName.new().

# Inside a class file.

# An inner class in this class file.
class SomeInnerClass:
    var a = 5


    func print_value_of_a():
        print(a)


# This is the constructor of the class file's main class.
func _init():
    var c = SomeInnerClass.new()
    c.print_value_of_a()

Classi come risorse

Le classi memorizzate come file sono trattate come risorse GDScript. È necessario caricarle dal disco per potervi accedere in altre classi. Lo si fa tramite le funzioni load o preload (vedere sotto). Per istanziare una risorsa caricata di una classe, si può chiamare la funzione new sull'oggetto della classe:

# Load the class resource when calling load().
var MyClass = load("myclass.gd")

# Preload the class only once at compile time.
const MyClass = preload("myclass.gd")


func _init():
    var a = MyClass.new()
    a.some_function()

Esportazioni

Nota

La documentazione sulle esportazioni è stata spostata in Proprietà esportate in GDScript.

Proprietà (setter e getter)

A volte, c'è bisogno che una variabile membro di una classe faccia di più che contenere dati, ed effettivamente convalidarli o elaborarli ogni volta che il suo valore cambia. Potrebbe anche essere opportuno incapsularne l'accesso in qualche modo.

A questo scopo, GDScript fornisce una sintassi speciale per definire le proprietà, grazie alle parole chiave set e get dopo la dichiarazione di una variabile. È quindi possibile definire un blocco di codice che verrà eseguito quando si accede alla variabile o quando la variabile viene assegnata.

Esempio:

var milliseconds: int = 0
var seconds: int:
    get:
        return milliseconds / 1000
    set(value):
        milliseconds = value * 1000

Nota

A differenza di setget nelle versioni precedenti di Godot, i metodi set e get vengono sempre chiamati (tranne quanto indicato di seguito), anche quando vi si accede all'interno della stessa classe (con o senza il prefisso self). Ciò rende il comportamento coerente. Se serve accedere direttamente al valore, bisogna utilizzare un'altra variabile per l'accesso diretto e assicurarsi che il codice della proprietà utilizzi quel nome.

Sintassi alternativa

Esiste anche un'altra notazione per utilizzare le funzioni esistenti della classe, se si desidera suddividere il codice dalla dichiarazione della variabile o riutilizzare il codice su più proprietà (ma non è possibile distinguere per quale proprietà viene chiamato il setter o getter):

var my_prop:
    get = get_my_prop, set = set_my_prop

Si può fare anche sulla stessa riga:

var my_prop: get = get_my_prop, set = set_my_prop

Il setter e il getter devono utilizzare la stessa notazione; non è consentito mischiare stili per la stessa variabile.

Nota

Non è possibile specificare suggerimenti del tipo per le funzioni setter e getter sulla stessa riga. Questo è fatto apposta per ridurre il boilerplate. Se la variabile è tipizzata, l'argomento del setter è automaticamente dello stesso tipo e il valore restituito dal getter deve corrispondere. Le funzioni setter e getter separate possono avere suggerimenti del tipo, e i loro tipi devono corrispondere al tipo della variabile o essere di tipo più ampio.

Quando i setter e getter non vengono chiamati

Quando una variabile viene inizializzata, il valore iniziale verrà scritto direttamente nella variabile, anche se l'annotazione @onready è applicata alla variabile.

Utilizzando il nome della variabile per impostarla all'interno del proprio setter o per ottenerla all'interno del proprio getter si accederà direttamente al membro sottostante, così da non creare un ciclo infinito e evitando di dover dichiarare esplicitamente un'altra variabile:

signal changed(new_value)
var warns_when_changed = "some value":
    get:
        return warns_when_changed
    set(value):
        changed.emit(value)
        warns_when_changed = value

Ciò si applica anche per la sintassi alternativa:

var my_prop: set = set_my_prop

func set_my_prop(value):
    my_prop = value # No infinite recursion.

Avvertimento

Questa eccezione non si propaga ad altre funzioni chiamate nel setter o nel getter. Ad esempio, il seguente codice provocherà una ricorsione infinita:

var my_prop:
    set(value):
        set_my_prop(value)

func set_my_prop(value):
    my_prop = value # Infinite recursion, since `set_my_prop()` is not the setter.

Modalità strumento (tool)

Normalmente, gli script non si eseguono all'interno dell'editor e solo le proprietà esportate si possono modificare. In alcuni casi, è preferibile che siano eseguiti all'interno dell'editor (a condizione che non eseguano codice di gioco o che non lo facciano apposta). Per fare ciò, si può inserire l'annotazione @tool all'inizio del file:

@tool
extends Button

func _ready():
    print("Hello")

Consultare Eseguire codice nell'editor per maggiori informazioni.

Avvertimento

Bisogna prestare attenzione quando si liberano nodi con queue_free() o free() in uno script strumento (soprattutto il proprietario dello script stesso). Poiché gli script strumento eseguono il loro codice nell'editor, un uso improprio potrebbe causarne l'arresto anomalo.

Gestione della memoria

Godot implementa il conteggio dei riferimenti per liberare determinate istanze che non sono più in uso, al posto di un collettore di memoria (garbage collector) o una gestione puramente manuale. Qualsiasi istanza della classe RefCounted (o qualsiasi classe che la eredita, come Resource) verrà liberata automaticamente quando non sarà più in uso. Per un'istanza di qualsiasi classe che non sia un RefCounted (come Node o il tipo base Object), rimarrà in memoria finché non verrà eliminata con free() (o queue_free() per i nodi).

Nota

Se un Node viene eliminato tramite free() o queue_free(), anche tutti i suoi nodi figlio verranno eliminati ricorsivamente.

Per evitare cicli di riferimento che non si possono liberare, viene fornita la funzione WeakRef per creare riferimenti deboli, i quali consentono di accedere all'oggetto senza impedire a un RefCounted di liberarsi. Ecco un esempio:

extends Node

var my_file_ref

func _ready():
    var f = FileAccess.open("user://example_file.json", FileAccess.READ)
    my_file_ref = weakref(f)
    # the FileAccess class inherits RefCounted, so it will be freed when not in use

    # the WeakRef will not prevent f from being freed when other_node is finished
    other_node.use_file(f)

func _this_is_called_later():
    var my_file = my_file_ref.get_ref()
    if my_file:
        my_file.close()

In alternativa, quando non si utilizzano riferimenti, è possibile usare is_instance_valid(instance) per verificare se un oggetto è stato liberato.

Segnali

I segnali sono uno strumento per emettere messaggi da un oggetto a cui altri oggetti possono reagire. Per creare segnali personalizzati per una classe, si usa la parola chiave signal.

extends Node


# A signal named health_depleted.
signal health_depleted

Nota

I segnali sono un meccanismo di callback. Svolgono anche il ruolo di osservatori, un modello di progettazione comune nella programmazione. Per ulteriori informazioni, leggere il tutorial sugli osservatori nell'ebook "Game Programming Patterns".

È possibile collegare questi segnali ai metodi nello stesso modo in cui si collegano i segnali integrati dei nodi quali Button o RigidBody3D.

Nell'esempio seguente, viene collegato il segnale health_depleted da un nodo Character a un nodo Game. Quando il nodo Character emette il segnale, verrà chiamato il metodo _on_character_health_depleted del nodo Game:

# game.gd

func _ready():
    var character_node = get_node('Character')
    character_node.health_depleted.connect(_on_character_health_depleted)


func _on_character_health_depleted():
    get_tree().reload_current_scene()

È possibile emettere tanti argomenti quanto si desiderano insieme a un segnale.

Ecco un esempio in cui questo è utile. Si suppone di volere una barra della vita sullo schermo che reagisce ai cambiamenti di salute con un'animazione, ma si desidera mantenere l'interfaccia utente separata dal giocatore nell'albero di scene.

Nello script character.gd, un segnale health_changed è definito ed emesso tramite il metodo Signal.emit(), e da un nodo Game più in alto nell'albero di scene, viene collegato al nodo Lifebar tramite il metodo Signal.connect():

# character.gd

...
signal health_changed


func take_damage(amount):
    var old_health = health
    health -= amount

    # We emit the health_changed signal every time the
    # character takes damage.
    health_changed.emit(old_health, health)
...
# lifebar.gd

# Here, we define a function to use as a callback when the
# character's health_changed signal is emitted.

...
func _on_Character_health_changed(old_value, new_value):
    if old_value > new_value:
        progress_bar.modulate = Color.RED
    else:
        progress_bar.modulate = Color.GREEN

    # Imagine that `animate` is a user-defined function that animates the
    # bar filling up or emptying itself.
    progress_bar.animate(old_value, new_value)
...

Nel nodo Game, si ottengono entrambi i nodi Character e Lifebar, poi viene collegato il personaggio, il quale emette il segnale, al ricevitore, in questo caso il nodo Lifebar.

# game.gd

func _ready():
    var character_node = get_node('Character')
    var lifebar_node = get_node('UserInterface/Lifebar')

    character_node.health_changed.connect(lifebar_node._on_Character_health_changed)

Ciò consente al nodo Lifebar di reagire ai cambiamenti di salute senza doverla accoppiare al nodo Character.

È possibile aggiungere dei nomi per gli argomenti facoltativi tra parentesi dopo la definizione del segnale:

# Defining a signal that forwards two arguments.
signal health_changed(old_value, new_value)

Questi argomenti appariranno nel pannello Segnali dell'editor, e Godot può utilizzarli per generare funzioni di callback. Tuttavia, è comunque possibile emettere un numero qualsiasi di argomenti quando si emettono segnali; spetta al programmatore emettere i valori corretti.

../../../_images/gdscript_basics_signals_node_tab_1.png

È anche possibile creare copie di oggetti Callable che accettano argomenti aggiuntivi usando Callable.bind(). Questo consente di aggiungere ulteriori informazioni alla connessione se il segnale emesso non fornisce accesso a tutti i dati necessari.

Quando il segnale viene emesso, il metodo di callback riceve i valori associati, in aggiunta a quelli forniti dal segnale.

Basandosi sull'esempio precedente, si suppone di voler visualizzare un registro dei danni subiti da ciascun personaggio sullo schermo, ad esempio Player1 took 22 damage. Il segnale health_changed non fornisce il nome del personaggio che ha subito danni. Quindi, quando si collega il segnale alla console di gioco, si può aggiungere il nome del personaggio tramite il metodo bind:

# game.gd

func _ready():
    var character_node = get_node('Character')
    var battle_log_node = get_node('UserInterface/BattleLog')

    character_node.health_changed.connect(battle_log_node._on_Character_health_changed.bind(character_node.name))

Il nodo BattleLog riceve ogni elemento associato come argomento aggiuntivo:

# battle_log.gd

func _on_Character_health_changed(old_value, new_value, character_name):
    if not new_value <= old_value:
        return

    var damage = old_value - new_value
    label.text += character_name + " took " + str(damage) + " damage."

Attendere segnali o coroutine

La parola chiave await si può utilizzare per creare coroutine che attendono l'emissione di un segnale prima di continuare l'esecuzione. L'utilizzo della parola chiave await con un segnale o con una chiamata a una funzione che è anche essa una coroutine restituirà immediatamente il controllo al chiamante. Quando il segnale viene emesso (o la coroutine chiamata termina), l'esecuzione riprenderà dal punto in cui si era interrotta.

Ad esempio, per interrompere l'esecuzione finché l'utente non preme un pulsante, si può fare qualcosa del genere:

func wait_confirmation():
    print("Prompting user")
    await $Button.button_up # Waits for the button_up signal from Button node.
    print("User confirmed")
    return true

In questo caso, wait_confirmation diventa una coroutine, il che significa che anche il chiamante deve attenderla:

func request_confirmation():
    print("Will ask the user")
    var confirmed = await wait_confirmation()
    if confirmed:
        print("User confirmed")
    else:
        print("User cancelled")

Si noti che richiedere il valore restituito di una coroutine senza await genererà un errore:

func wrong():
    var confirmed = wait_confirmation() # Will give an error.

Tuttavia, se il risultato non importa, è possibile semplicemente chiamarla in modo asincrono, il che non interromperà l'esecuzione e non trasformerà la funzione attuale in una coroutine:

func okay():
    wait_confirmation()
    print("This will be printed immediately, before the user press the button.")

Se si utilizza await con un'espressione che non è un segnale né una coroutine, il valore verrà restituito immediatamente e la funzione non restituirà il controllo al chiamante:

func no_wait():
    var x = await get_five()
    print("This doesn't make this function a coroutine.")

func get_five():
    return 5

Inoltre ciò significa che se un segnale viene restituito da una funzione che non è una coroutine, il chiamante sarà costretto ad attendere il segnale:

func get_signal():
    return $Button.button_up

func wait_button():
    await get_signal()
    print("Button was pressed")

Nota

A differenza di yield nelle precedenti versioni di Godot, non è possibile ottenere l'oggetto di stato della funzione. Ciò è per garantire la sicurezza del tipo. In questo modo, una funzione non può dichiarare di restituire un int mentre in realtà restituisce un oggetto di stato della funzione durante l'esecuzione.

È possibile memorizzare gli argomenti passati ai parametri del segnale. Se c'è un solo parametro, il valore atteso avrà lo stesso tipo dell'argomento:

func toggled():
    var signal_args = await $Button.toggled
    assert(typeof(signal_args) == TYPE_BOOL)

Se c'è più di un parametro, il valore atteso sarà di tipo Array:

func request_completed():
    var signal_args = await $HTTPRequest.request_completed
    assert(typeof(signal_args) == TYPE_ARRAY)

Altrimenti, il valore atteso sarà null:

func button_up():
    var signal_args = await $Button.button_up
    assert(signal_args == null)

Parola chiave Assert

La parola chiave assert serve per verificare condizioni nelle build di debug. Queste asserzioni sono ignorate nelle build non di debug. Ciò significa che l'espressione passata come argomento non sarà valutata in un progetto esportato in modalità di rilascio. Per questo motivo, le asserzioni non devono contenere espressioni con effetti collaterali. Altrimenti, il comportamento dello script varierà a seconda che sia eseguito in una build di debug o di rilascio.

# Check that 'i' is 0. If 'i' is not 0, an assertion error will occur.
assert(i == 0)

Quando si esegue un progetto dall'editor, il progetto verrà messo in pausa in caso di errore di asserzione.

È possibile passare facoltativamente un messaggio di errore personalizzato da visualizzare se l'asserzione fallisce:

assert(enemy_power < 256, "Enemy is too powerful!")