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.

Introduzione alla fisica

Nello sviluppo di videogiochi, spesso è necessario sapere quando due oggetti nel gioco si intersecano o entrano in contatto. Ciò è conosciuto come rilevamento delle collisioni. Quando viene rilevata una collisione, in genere si desidera che succeda qualcosa. Ciò è conosciuta come risposta alle collisioni.

Godot offre vari oggetti di collisione in 2D e 3D per fornire sia il rilevamento sia la risposta alle collisioni. Decidere quale utilizzare per il proprio progetto può portare confusione. È possibile evitare problemi e semplificare lo sviluppo se si comprende come funziona ciascuno di essi e quali sono i loro vantaggi e svantaggi.

In questa guida imparerai:

  • I quattro tipi di oggetti di collisione in Godot

  • Come funziona ciascun oggetto di collisione

  • Quando e perché scegliere un tipo piuttosto che un altro

Nota

Gli esempi di questo documento utilizzeranno oggetti 2D. Ogni oggetto fisico 2D e ogni forma di collisione ha un equivalente diretto in 3D e nella maggior parte dei casi funzionano allo stesso modo.

Avvertimento

La fisica in Godot, a prescindere dal motore di fisica utilizzato, non è deterministica. La natura del determinismo dei motori di fisica è molto complessa e ha a che fare con numerosi fattori, il che significa che non è garantito che la fisica si comporti allo stesso modo in situazioni apparentemente identiche.

Oggetti di collisione

Godot offre quattro tipi di oggetti di collisione, tutti estendono CollisionObject2D. Gli ultimi tre elencati di seguito sono corpi fisici e estendono ulteriormente PhysicsBody2D.

  • Area2D

    I nodi Area2D forniscono il rilevamento e l'influenza. Possono rilevare quando oggetti si sovrappongono e possono emettere segnali quando corpi entrano o escono. Un nodo Area2D si può anche utilizzare per sovrascrivere le proprietà fisiche, come la gravità o lo smorzamento, in un'area definita.

  • StaticBody2D

    Un corpo statico è un corpo che non viene mosso dal motore fisico. Partecipa al rilevamento delle collisioni, ma non si muove in risposta alla collisione. Sono spesso utilizzati per oggetti che fanno parte dell'ambiente o che non necessitano di alcun comportamento dinamico.

  • RigidBody2D

    Questo è il nodo che implementa la fisica 2D simulata. Non si controlla direttamente un RigidBody2D, ma si applicano forze (gravità, impulsi, ecc.) e il motore fisico ne calcola il movimento risultante. Leggi di più sull'utilizzo dei corpi rigidi

  • CharacterBody2D

    Un corpo che fornisce il rilevamento delle collisioni, ma senza fisica. Tutti i movimenti e la risposta alle collisioni si devono implementare in codice.

Materiale di fisica

È possibile configurare i corpi statici e rigidi per usare un PhysicsMaterial. Questo permette di regolare l'attrito e il rimbalzo di un oggetto, nonché impostare se è assorbente e/o ruvido.

Forme di collisione

Un corpo fisico può contenere un numero qualsiasi di oggetti Shape2D come figli. Queste forme sono utilizzate per definire i limiti di collisione dell'oggetto e per rilevare contatti con altri oggetti.

Nota

Per rilevare le collisioni, all'oggetto deve essere assegnato almeno uno Shape2D.

Il modo più comune per assegnare una forma è aggiungere CollisionShape2D o CollisionPolygon2D come nodi figlio dell'oggetto. Questi nodi consentono di disegnare la forma direttamente nello spazio di lavoro dell'editor.

Importante

Attenzione a non ridimensionare mai le forme di collisione nell'editor. La proprietà "Scale" nell'Ispettore dovrebbe rimanere (1, 1). Quando si cambiano le dimensioni della forma di collisione, si dovrebbero sempre utilizzare le maniglie di ridimensionamento, non le maniglie della scala Node2D. Cambiare la scala di una forma può causare comportamenti inaspettati.

../../_images/player_coll_shape.webp

Callback del processo di fisica

Il motore di fisica funziona a una velocità fissa (predefinita di 60 iterazioni al secondo). Questa velocità è in genere diversa dal frame rate, che varia in base a cosa è renderizzato e alle risorse disponibili.

È importante che tutto il codice relativo alla fisica sia eseguito a questa frequenza fissa. Pertanto, Godot distingue tra elaborazione fisica ed elaborazione inattiva. Il codice che è eseguito a ogni frame è chiamato elaborazione inattiva, mentre quello che è eseguito a ogni tick di fisica è chiamato elaborazione fisica. Godot fornisce due diversi callback, uno per ciascuno di queste frequenze di elaborazione.

Il callback di fisica, Node._physics_process(), viene chiamato prima di ogni passaggio di fisica. Qualsiasi codice che ha bisogno di accedere alle proprietà di un corpo deve essere eseguito qui. A questo metodo sarà passato un parametro delta, che è un numero in virgola mobile pari al tempo trascorso in secondi dall'ultimo passaggio. Con la frequenza di aggiornamento fisica predefinita di 60 Hz, sarà in genere uguale a 0.01666... (ma non sempre, vedi di seguito).

Nota

Si consiglia di utilizzare sempre il parametro delta quando importa nei calcoli di fisica, in modo che il gioco si comporti correttamente se si modifica la frequenza di aggiornamento fisica o se il dispositivo del giocatore non riesce a tenere il passo.

Strati e maschere di collisione

Una delle funzionalità di collisione più potenti, ma spesso fraintesa, è il sistema degli strati di collisione. Questo sistema consente di creare interazioni complesse tra una varietà di oggetti. I concetti chiave sono strati e maschere. Ogni CollisionObject2D ha 32 diversi strati di fisica con cui può interagire.

Diamo un'occhiata a ciascuna proprietà:

  • collision_layer

    Questa descrive gli strati in cui appare l'oggetto. Come predefinito, tutti i corpi si trovano sullo strato 1.

  • collision_mask

    Questa descrive quali strati il corpo scansionerà per rilevare le collisioni. Se un oggetto non si trova in uno degli strati nella maschera, il corpo lo ignorerà. Come predefinito, tutti i corpi scansionano lo strato 1.

Queste proprietà si possono configurare tramite codice oppure modificandole nell'Ispettore.

Tenere traccia di che cosa serve ogni strato può essere difficile, quindi potrebbe essere utile assegnare un nome agli strati che si stanno utilizzando. È possibile assegnare nomi in Impostazioni del progetto > Nomi di strati> Fisica 2D.

../../_images/physics_layer_names.webp

Esempio di GUI

Nel gioco ci sono quattro tipi di nodi: Walls (Muri), Player (Giocatore), Enemy (Nemico) e Coin (Moneta). Sia Player sia Enemy dovrebbero entrare in collisione con Walls. Il nodo Player dovrebbe rilevare le collisioni sia con Enemy sia con la Coin, ma Enemy e Coin dovrebbero ignorarsi a vicenda.

Inizia assegnando agli strati 1 a 4 i nomi "walls", "player", "enemies" e "coins" e posiziona ogni tipo di nodo nel rispettivo strato attraverso la proprietà "Layer". Quindi imposta la proprietà "Mask" di ciascun nodo selezionando gli strati con cui deve interagire. Ad esempio, le impostazioni di Player apparirebbero così:

../../_images/player_collision_layers.webp ../../_images/player_collision_mask.webp

Esempio in codice

Nelle chiamate di funzioni, gli strati sono specificati sotto forma di una maschera di bit. Quando una funzione abilita tutti gli strati per valore predefinito, la maschera di strati sarà fornita come 0xffffffff. Il codice può utilizzare la notazione binaria, esadecimale o decimale per le maschere di strati, a seconda delle preferenze.

L'equivalente in codice all'esempio precedente, in cui sono stati abilitati gli strati 1, 3 e 4, sarebbe il seguente:

# Example: Setting mask value for enabling layers 1, 3 and 4

# Binary - set the bit corresponding to the layers you want to enable (1, 3, and 4) to 1, set all other bits to 0.
# Note: Layer 32 is the first bit, layer 1 is the last. The mask for layers 4, 3 and 1 is therefore:
0b00000000_00000000_00000000_00001101
# (This can be shortened to 0b1101)

# Hexadecimal equivalent (1101 binary converted to hexadecimal).
0x000d
# (This value can be shortened to 0xd.)

# Decimal - Add the results of 2 to the power of (layer to be enabled - 1).
# (2^(1-1)) + (2^(3-1)) + (2^(4-1)) = 1 + 4 + 8 = 13
#
# We can use the `<<` operator to shift the bit to the left by the layer number we want to enable.
# This is a faster way to multiply by powers of 2 than `pow()`.
# Additionally, we use the `|` (binary OR) operator to combine the results of each layer.
# This ensures we don't add the same layer multiple times, which would behave incorrectly.
(1 << 1 - 1) | (1 << 3 - 1) | (1 << 4 - 1)

# The above can alternatively be written as:
# pow(2, 1 - 1) + pow(2, 3 - 1) + pow(2, 4 - 1)

È anche possibile impostare i bit singolarmente chiamando set_collision_layer_value(layer_number, value) o set_collision_mask_value(layer_number, value) su qualsiasi CollisionObject2D come segue:

# Example: Setting mask value to enable layers 1, 3, and 4.

var collider: CollisionObject2D = $CollisionObject2D  # Any given collider.
collider.set_collision_mask_value(1, true)
collider.set_collision_mask_value(3, true)
collider.set_collision_mask_value(4, true)

È possibile utilizzare le annotazioni di esportazione per esportare le maschere di bit nell'editor con un'interfaccia intuitiva:

@export_flags_2d_physics var layers_2d_physics

Sono disponibili ulteriori annotazioni di esportazione per gli strati di rendering e navigazione, sia in 2D sia in 3D. Consultare Esportare bit flag.

Area2D

I nodi di area forniscono il rilevamento e l'influenza. Possono rilevare quando oggetti si sovrappongono e possono emettere segnali quando corpi entrano o escono. Le aree si può anche utilizzare per sovrascrivere le proprietà fisiche, come la gravità o lo smorzamento, in un'area definita.

Ci sono tre usi principali per Area2D:

  • Sovrascrivere i parametri fisici (come la gravità) in una determinata regione.

  • Rilevare quando altri corpi entrano o escono da una regione o quali corpi si trovano attualmente in una regione.

  • Verificare sovrapposizioni con altre aree.

Come predefinito, le aree ricevono anche gli input del mouse e tattile.

StaticBody2D

Un corpo statico è un corpo che non viene mosso dal motore fisico. Partecipa al rilevamento delle collisioni, ma non si muove in risposta alla collisione. Tuttavia, può impartire movimento o rotazione a un corpo in collisione come se fosse in movimento, utilizzando le sue proprietà constant_linear_velocity e constant_angular_velocity.

I nodi StaticBody2D sono spesso utilizzati per oggetti che fanno parte dell'ambiente o che non necessitano di alcun comportamento dinamico.

Esempi d'uso per StaticBody2D:

  • Piattaforme (incluse piattaforme in movimento)

  • Nastri trasportatori

  • Muri e altri ostacoli

RigidBody2D

Questo è il nodo che implementa la fisica 2D simulata. Non si controlla un RigidBody2D direttamente, ma si applicano forze e il motore di fisica ne calcola il movimento risultante, incluse le collisioni con altri corpi, e le risposte alle collisioni, come rimbalzi, rotazioni, ecc.

È possibile modificare il comportamento di un corpo rigido tramite proprietà quali "Mass" (massa), "Friction" (attrito) o "Bounce" (rimbalzo), che si possono impostare nell'Ispettore.

Il comportamento del corpo è influenzato anche dalle proprietà del mondo, come impostate in Impostazioni del progetto > Fisica, oppure entrando in un Area2D che sovrascrive le proprietà fisiche globali.

Quando un corpo rigido è fermo e non si muove per un po', entra in riposo. Un corpo in riposo si comporta come un corpo statico e le sue forze non sono calcolate dal motore di fisica. Il corpo si risveglierà quando vengono applicate delle forze, sia tramite una collisione, sia tramite codice.

Utilizzare RigidBody

Uno dei vantaggi di utilizzare un corpo rigido è che è possibile ottenere molti comportamenti "gratuitamente" senza scrivere codice. Ad esempio, se si stesse creando un gioco in stile "Angry Birds" con blocchi cadenti, basterebbe creare RigidBody2D e modificarne le proprietà. L'impilamento, la caduta e il rimbalzo verrebbero calcolati automaticamente dal motore di fisica.

Tuttavia, se si desidera avere un certo controllo sul corpo, è necessario fare attenzione: modificare position, linear_velocity o altre proprietà fisiche di un corpo rigido può causare comportamenti imprevisti. Se è necessario modificare una qualsiasi delle proprietà fisiche, si consiglia di usare il callback _integrate_forces() anziché _physics_process(). In questo callback, si ha accesso al PhysicsDirectBodyState2D del corpo, che consente di modificare in modo sicuro le proprietà e di sincronizzarle con il motore di fisica.

Ad esempio, ecco il codice per un'astronave in stile "Asteroids":

extends RigidBody2D

var thrust = Vector2(0, -250)
var torque = 20000

func _integrate_forces(state):
    if Input.is_action_pressed("ui_up"):
        state.apply_force(thrust.rotated(rotation))
    else:
        state.apply_force(Vector2())
    var rotation_direction = 0
    if Input.is_action_pressed("ui_right"):
        rotation_direction += 1
    if Input.is_action_pressed("ui_left"):
        rotation_direction -= 1
    state.apply_torque(rotation_direction * torque)

Da notare come non stiamo impostando direttamente le proprietà linear_velocity o angular_velocity, ma stiamo applicando delle forze (thrust e torque) al corpo e lasciando che il motore di fisica calcoli il movimento risultante.

Nota

Quando un corpo rigido entra in riposo, la funzione _integrate_forces() non verrà chiamata. Per ignorare questo comportamento, sarà necessario mantenere il corpo attivo creando una collisione, applicandogli una forza o disabilitando la proprietà can_sleep. Si tenga presente che questo può avere un effetto negativo sulle prestazioni.

Riportare i contatti

Come predefinito, i corpi rigidi non tengono traccia dei contatti, poiché ciò può richiedere un'enorme quantità di memoria se nella scena sono presenti molti corpi. Per abilitare il rapporto dei contatti, impostare la proprietà max_contacts_reported su un valore diverso da zero. I contatti si possono quindi ottenere tramite PhysicsDirectBodyState2D.get_contact_count() e funzioni correlate.

Il monitoraggio dei contatti tramite segnali si può abilitare tramite la proprietà contact_monitor. Vedere RigidBody2D per l'elenco dei segnali disponibili.

CharacterBody2D

I corpi CharacterBody2D rilevano le collisioni con altri corpi, ma non sono influenzati da proprietà fisiche come gravità o attrito. Si devono invece controllare dall'utente tramite codice. Il motore di fisica non muoverà un corpo di personaggio.

Quando si muove il corpo di un personaggio, non si dovrebbe impostare direttamente la sua position. Si utilizzano invece i metodi move_and_collide() o move_and_slide(). Questi metodi muovono il corpo lungo un determinato vettore e si arrestano immediatamente se viene rilevata una collisione con un altro corpo. Dopo che il corpo è entrato in collisione, qualsiasi risposta alla collisione deve essere programmata manualmente.

Riposta alle collisioni di carattere

Dopo una collisione, si potrebbe voler far rimbalzare il corpo, farlo scivolare lungo un muro o alterare le proprietà dell'oggetto che ha colpito. Il modo in cui si gestisce la risposta alle collisioni dipende dal metodo utilizzato per muovere il CharacterBody2D.

move_and_collide

Quando si utilizza move_and_collide(), la funzione restituisce un oggetto KinematicCollision2D, che contiene informazioni sulla collisione e sul corpo in collisione. È possibile utilizzare queste informazioni per determinare la risposta.

Ad esempio, se si desidera trovare il punto nello spazio in cui si è verificata la collisione:

extends PhysicsBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        var collision_point = collision_info.get_position()

Oppure per rimbalzare sull'oggetto in collisione:

extends PhysicsBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        velocity = velocity.bounce(collision_info.get_normal())

move_and_slide

Lo scivolamento è una risposta comune di collisione: immaginare un giocatore che si muove lungo i muri in un gioco dall'alto, o che corre su e giù per le pendenza in un piattaforme. Sebbene sia possibile programmare questa risposta manualmente dopo aver utilizzato move_and_collide(), move_and_slide() offre un modo pratico per implementare un movimento scivolante senza dover scrivere molto codice.

Avvertimento

move_and_slide() include automaticamente il passo temporale nel suo calcolo, quindi non bisogna moltiplicare il vettore di velocità per delta. Questo non si applica a gravity, poiché è un'accelerazione e dipende dal tempo, e deve essere scalata di delta.

Ad esempio, usa il seguente codice per creare un personaggio capace di camminare sul terreno (anche in pendenza) e saltare quando si trova a terra:

extends CharacterBody2D

var run_speed = 350
var jump_speed = -1000
var gravity = 2500

func get_input():
    velocity.x = 0
    var right = Input.is_action_pressed('ui_right')
    var left = Input.is_action_pressed('ui_left')
    var jump = Input.is_action_just_pressed('ui_select')

    if is_on_floor() and jump:
        velocity.y = jump_speed
    if right:
        velocity.x += run_speed
    if left:
        velocity.x -= run_speed

func _physics_process(delta):
    velocity.y += gravity * delta
    get_input()
    move_and_slide()

Consultare Personaggio cinematico (2D) per maggiori dettagli sull'utilizzo di move_and_slide(), incluso un progetto di dimostrazione con codice dettagliato.