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.

Matematica vettoriale

Introduzione

Questo tutorial è un'introduzione breve e pratica all'algebra lineare applicata allo sviluppo di giochi. L'algebra lineare è lo studio dei vettori e dei loro utilizzi. I vettori hanno numerose applicazioni nello sviluppo sia 2D sia 3D e Godot li usa ampiamente. Sviluppare una buona comprensione della matematica vettoriale è essenziale per diventare un bravo sviluppatore di giochi.

Nota

Questo tutorial non è un libro di testo formale sull'algebra lineare. Ci limiteremo a vedere come è applicata allo sviluppo di giochi. Per una panoramica più ampia sulla matematica, consultare https://www.khanacademy.org/math/linear-algebra

Sistemi di coordinate (2D)

Nello spazio 2D, le coordinate sono definite da un asse orizzontale (x) e un asse verticale (y). Una posizione specifica nello spazio 2D è scritta come una coppia di valori come (4, 3).

../../_images/vector_axis1.png

Nota

Se sei nuovo con la computer grafica, potrebbe sembrare strano che l'asse y positivo punti verso il basso invece che verso l'alto, come probabilmente hai imparato a lezione di matematica. Tuttavia, questo è comune nella maggior parte delle applicazioni di computer grafica.

Qualsiasi posizione nel piano 2D può essere identificata da una coppia di numeri in questo modo. Tuttavia, possiamo anche pensare alla posizione (4, 3) come a una differenza dal punto (0, 0), o origine. Disegna una freccia che punta dall'origine al punto:

../../_images/vector_xy1.png

Questo è un vettore. Un vettore rappresenta molte utili informazioni. Oltre a dirci che il punto si trova a (4, 3), possiamo anche considerarlo come un angolo θ (theta) e una lunghezza (o modulo, magnitudine) m. In questo caso, la freccia è un vettore di posizione - indica una posizione nello spazio, relativa all'origine.

Un aspetto molto importante da considerare riguardo ai vettori è che rappresentano solo direzione e modulo relativi. Non esiste il concetto di posizione di un vettore. I seguenti due vettori sono identici:

../../_images/vector_xy2.png

Entrambi i vettori rappresentano un punto 4 unità a destra e 3 unità sotto un punto di partenza. Non importa in quale punto del piano si traccia il vettore, rappresenta sempre una direzione e un modulo relativi.

Operazioni vettoriali

È possibile utilizzare entrambi i metodi (coordinate x e y, angolo e modulo) per riferirsi a un vettore, ma per comodità, i programmatori in genere utilizzano la notazione di coordinate. Ad esempio, in Godot, l'origine è l'angolo in alto a sinistra dello schermo, quindi per posizionare un nodo 2D denominato Node2D 400 pixel a destra e 300 pixel in basso, utilizza il seguente codice:

$Node2D.position = Vector2(400, 300)

Godot supporta sia Vector2 sia Vector3, rispettivamente per l'utilizzo in 2D e 3D. Le stesse regole matematiche discusse in questo articolo si applicano a entrambi i tipi e, ogni volta che includiamo collegamenti ai metodi di Vector2 nel riferimento alle classi, puoi anche consultare le loro controparti di Vector3.

Accesso ai membri

È possibile accedere direttamente ai singoli componenti del vettore per nome.

# Create a vector with coordinates (2, 5).
var a = Vector2(2, 5)
# Create a vector and assign x and y manually.
var b = Vector2()
b.x = 3
b.y = 1

Addizione di vettori

Quando si sommano o si sottraggono due vettori, i componenti corrispondenti sono sommati:

var c = a + b  # (2, 5) + (3, 1) = (5, 6)

Possiamo anche visualizzarlo aggiungendo il secondo vettore alla fine del primo:

../../_images/vector_add1.png

Nota che sommare a + b dà lo stesso risultato di b + a.

Moltiplicazione scalare

Nota

I vettori rappresentano sia la direzione sia la magnitudine (o modulo). Un valore che rappresenta solo la magnitudine è chiamato scalare. Gli scalari usano il tipo float in Godot.

Un vettore può essere moltiplicato per uno scalare:

var c = a * 2  # (2, 5) * 2 = (4, 10)
var d = b / 3  # (3, 6) / 3 = (1, 2)
var e = d * -2 # (1, 2) * -2 = (-2, -4)
../../_images/vector_mult1.png

Nota

Moltiplicare un vettore per uno scalare positivo non ne cambia la direzione, ma solo il modulo. Moltiplicare per uno scalare negativo produce un vettore nella direzione opposta. Così si scala un vettore.

Applicazioni pratiche

Diamo un'occhiata a due utilizzi comuni dell'addizione e della sottrazione vettoriale.

Movimento

Un vettore può rappresentare qualsiasi grandezza con modulo e direzione. Esempi tipici sono: posizione, velocità, accelerazione e forza. In questa immagine, al primo passo, l'astronave ha un vettore posizione pari a (1, 3) e un vettore velocità pari a (2, 1). Il vettore velocità rappresenta la distanza percorsa dall'astronave a ogni passo. Possiamo ricavare la posizione al secondo passo sommando la velocità alla posizione attuale.

../../_images/vector_movement1.png

Suggerimento

La velocità misura la variazione di posizione per unità di tempo. La nuova posizione è ricavata sommando la velocità moltiplicata per il tempo trascorso (qui si presume un'unità, ad esempio 1 s) alla posizione precedente.

In un tipico scenario di gioco 2D, dovresti avere una velocità in pixel al secondo e moltiplicarla per il parametro delta (tempo trascorso dal frame precedente) dai callback _process() o _physics_process().

Puntare verso un bersaglio

In questo scenario, abbiamo un carro armato che desidera puntare la sua torretta verso un robot. Sottraendo la posizione del carro armato da quella del robot, si ottiene il vettore che punta dal carro armato al robot.

../../_images/vector_subtract2.webp

Suggerimento

Per ricavare un vettore che punta da A a B, usa B - A.

Vettori unitari

Un vettore con modulo pari a 1 è chiamato vettore unitario. A volte sono anche chiamati vettori di direzione o normali. I vettori unitari sono utili quando si deve tenere traccia di una direzione.

Normalizzazione

Normalizzare un vettore significa ridurne la lunghezza a 1 preservandone la direzione. Ciò si ottiene dividendo ciascuna delle sue componenti per il suo modulo. Poiché si tratta di un'operazione così comune, Godot fornisce un metodo normalized() dedicato per questo:

a = a.normalized()

Avvertimento

Poiché la normalizzazione richiede la divisione per la lunghezza del vettore, non è possibile normalizzare un vettore di lunghezza 0. Tentare di farlo solitamente genera un errore. In GDScript, tuttavia, se si prova a chiamare il metodo normalized() su un vettore di lunghezza 0, il valore viene lasciato intatto e l'errore viene evitato.

Riflessione

Un utilizzo comune dei vettori unitari è quello di indicare le normali. I vettori normali sono vettori unitari allineati perpendicolarmente a una superficie, che ne definiscono la direzione. Sono comunemente utilizzati per l'illuminazione, le collisioni e altre operazioni che riguardano superfici.

Ad esempio, immaginiamo di avere una palla in movimento che vogliamo far rimbalzare contro un muro o un altro oggetto:

../../_images/vector_reflect1.png

La normale alla superficie ha un valore di (0, -1) perché è una superficie orizzontale. Quando la palla si scontra, prendiamo il suo movimento rimanente (la quantità che rimane quando colpisce la superficie) e lo riflettiamo utilizzando la normale. In Godot, esiste il metodo bounce() per gestire ciò. Ecco un esempio di codice del diagramma sopra utilizzando un CharacterBody2D:

var collision: KinematicCollision2D = move_and_collide(velocity * delta)
if collision:
    var reflect = collision.get_remainder().bounce(collision.get_normal())
    velocity = velocity.bounce(collision.get_normal())
    move_and_collide(reflect)

Prodotto scalare

Il prodotto scalare è uno dei concetti più importanti nella matematica vettoriale, ma è spesso incompreso. Il prodotto scalare è un'operazione su due vettori che restituisce uno scalare. A differenza di un vettore, che contiene sia modulo sia direzione, un valore scalare ha solo modulo.

La formula del prodotto scalare assume due forme comuni:

../../_images/vector_dot1.png

e

../../_images/vector_dot2.png

La notazione matematica ||A|| rappresenta il modulo del vettore A, e Ax indica la componente x del vettore A.

Tuttavia, nella maggior parte dei casi è più semplice utilizzare il metodo integrato dot(). Nota che l'ordine dei due vettori non è importante:

var c = a.dot(b)
var d = b.dot(a)  # These are equivalent.

Il prodotto scalare è particolarmente utile se utilizzato con vettori unitari, riducendo la prima formula soltanto in cos(θ). Questo significa che possiamo usare il prodotto scalare per ottenere informazioni sull'angolo tra due vettori:

../../_images/vector_dot3.png

Quando si utilizzano vettori unitari, il risultato sarà sempre compreso tra -1 (180°) e 1 (0°).

Orientazione

Possiamo usare questo fatto per rilevare se un oggetto è rivolto verso un altro oggetto. Nel diagramma sottostante, il giocatore P sta cercando di evitare gli zombi A e B. Supponendo che il campo visivo di uno zombi sia di 180°, riesce a vedere il giocatore?

../../_images/vector_facing2.png

Le frecce verdi fA e fB sono vettori unitari che rappresentano la direzione in cui è rivolto lo zombi e il semicerchio blu rappresenta il suo campo visivo. Per lo zombi A, troviamo il vettore di direzione AP, che punta al giocatore, tramite P - A e lo normalizziamo. Tuttavia, Godot ha un metodo ausiliare per fare ciò chiamato direction_to(). Se l'angolo tra questo vettore e il vettore rivolto è inferiore a 90°, lo zombi può vedere il giocatore.

Nel codice, apparirebbe così:

var AP = A.direction_to(P)
if AP.dot(fA) > 0:
    print("A sees P!")

Prodotto vettoriale

Come il prodotto scalare, il prodotto vettoriale è un'operazione su due vettori. Tuttavia, il risultato del prodotto vettoriale è un vettore con una direzione che è perpendicolare a entrambi. Il suo modulo dipende dal loro angolo relativo. Se due vettori sono paralleli, il risultato del loro prodotto vettoriale sarà un vettore nullo.

../../_images/vector_cross1.png ../../_images/vector_cross2.png

Il prodotto vettoriale si calcola così:

var c = Vector3()
c.x = (a.y * b.z) - (a.z * b.y)
c.y = (a.z * b.x) - (a.x * b.z)
c.z = (a.x * b.y) - (a.y * b.x)

Con Godot, puoi utilizzare il metodo integrato Vector3.cross():

var c = a.cross(b)

Il prodotto vettoriale non è definito matematicamente in 2D. Il metodo Vector2.cross() è un equivalente comunemente utilizzato del prodotto vettoriale 3D per i vettori 2D.

Nota

Nel prodotto vettoriale, l'ordine è importante. a.cross(b) non dà lo stesso risultato di b.cross(a). I vettori risultanti puntano in direzioni opposte.

Calcolo delle normali

Un utilizzo comune dei prodotti vettoriali è ricavare la normale a una superficie o a un piano nello spazio 3D. Se abbiamo il triangolo AB, possiamo utilizzare la sottrazione vettoriale per trovare due lati AB e AC. Attraverso il prodotto vettoriale AB × AC, si ottiene un vettore perpendicolare a entrambi: la normale alla superficie.

Ecco una funzione per calcolare la normale di un triangolo:

func get_triangle_normal(a, b, c):
    # Find the surface normal given 3 vertices.
    var side1 = b - a
    var side2 = c - a
    var normal = side1.cross(side2)
    return normal

Puntare verso un bersaglio

Nella sezione precedente sul prodotto scalare, abbiamo visto come questo si possa utilizzare per calcolare l'angolo tra due vettori. Tuttavia, in 3D, questa informazione non è sufficiente. Dobbiamo anche sapere attorno a quale asse ruotare. Possiamo ricavarlo calcolando il prodotto vettoriale tra la direzione attuale e la direzione del bersaglio. Il vettore perpendicolare risultante è l'asse di rotazione.

Maggiori informazioni

Per maggiori informazioni sull'utilizzo della matematica vettoriale in Godot, consulta i seguenti articoli: