Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Raycasting: non solo traiettorie degli spari

Emettere o "sparare" (cast) raggi in un ambiente 3D e controllare le collisioni risultanti
Emettere o "sparare" (cast) raggi in un ambiente 3D e controllare le collisioni risultanti
Link copiato negli appunti

Parliamo di Raycasting, ovvero la tecnica utilizzata per emettere o "sparare" (cast) raggi in un ambiente 3D e controllare le collisioni risultanti.

I raggi e la classe Ray

I raggi utilizzati nel Raycasting sono linee invisibili che hanno un punto d'origine, sono infinite ed orientate lungo una direzione (non sono quindi come le "rette" infinite in matematica). Si possono usare per numerosissimi scopi: principalmente negli shooter per creare linee di tiro per armi da fuoco. Possiamo utilizzare i raggi anche per controllare se fra il giocatore ed un oggetto c'è una linea di visuale, o per valutare la distanza fra due oggetti (che non è la distanza che otterremmo se facessimo un semplice Vector3.Distance: quella è la distanza fra i loro pivot!).

Per descrivere i raggi, in Unity esiste la classe Ray che usa due valori Vector3: un punto di origine, ed un orientamento. Il raggio quindi parte da un punto nello spazio, e viene proiettato all'infinito nella direzione indicata dal vettore orientamento (il quale però ha lunghezza 1, come tutti i vettori orientamento).

Per creare un raggio in avanti a partire dalla posizione di un oggetto:

Ray forwardRay = new Ray(transform.position, transform.forward);

Unica cosa degna di nota della classe Ray è la funzione GetPoint, che permette di recuperare un punto lungo il raggio, ad una distanza specificata dall'origine del raggio.

Come fare il raycast

Dopo aver creato il raggio, per effettuare il raycast si usa una funzione statica della classe Physics chiamata appunto Raycast, che spara un raggio e lo testa con tutti i Collider della scena. Questa funzione restituisce un valor booleano che indica se il raggio ha colpito un qualunque Collider o no. Per questo motivo, spesso Raycast viene usata all'interno di un costrutto condizionale (if, while).

Esistono più versioni di questa funzione, che può accettare diversi parametri. Facciamo un esempio semplice con solo un parametro di tipo Ray, ed una lunghezza massima del raggio:

Ray forwardRay = new Ray(transform.position, transform.forward);
if (Physics.Raycast(forwardRay, 100f)
{
    Debug.Log ("Qualcosa è stato colpito. Ma cosa?");
}

In questo caso, Raycast ci dirà solo se il raggio ha incontrato un collider durante la sua corsa, ma non sappiamo quale collider sia stato colpito, né dove. Se il raggio non incontra Collider entro 100 unità (il secondo parametro float), Raycast restituirà false. Adesso all'interno dell'if possiamo inserire tutte le istruzioni da eseguire in caso la collisione sia avvenuta.

Se vogliamo proiettare un raggio infinito, al posto della lunghezza (100f nell'esempio sopra) si può utilizzare Mathf.Infinity, che rappresenta un numero infinito.

Informazioni sulla collisione: RaycastHit

Altra classe fondamentale per il raycasting, RaycastHit è un tipo che viene usato per contenere informazioni sul risultato del raycasting, ovvero sulla collisione che il raggio effettua con il primo corpo incontrato (ammesso che ne incontri uno).

Per farci restituire un oggetto di tipo RaycastHit con tutte le informazioni sulla collisione, dobbiamo innanzitutto crearne uno nullo, e "darlo in pasto" alla funzione Raycast. Questa ce lo restituirà con tutte le informazioni disponibili sulla collisione fra il raggio ed un collider.

Per passare un parametro per riferimento ad una funzione usiamo la parola chiave out (trovate qualche informazione in più qui):

Ray ray = new Ray(transform.position, transform.forward);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100f))
{
    Debug.Log(hit.collider.name);
}

In questo modo, la variabile hit viene passata nulla alla funzione, che la inizializza e ne valorizza i parametri, ma solo nel caso in cui il raggio incontri qualcosa.

I parametri più importanti della collisione sono principalmente le proprietà collider e transform, per accedere ai componenti del gameObject colpito (nell'esempio sopra stampiamo il nome del Collider). Se questi aveva un componente Rigidbody, la proprietà rigidbody conterrà un riferimento ad esso, altrimenti sarà null.

Con point (che è una posizione nello spazio espressa tramite Vector3) si può creare un effetto (come un esplosione) nel punto esatto dell'impatto di un colpo di arma, mentre con normal si può ottenere un vettore direzione che punta in perpendicolare rispetto al poligono colpito dal raggio: in questo modo si può creare un effetto di rimbalzo di un colpo.

Con l'unione delle due informazioni si può sparare un altro raggio che rimbalzi sulla superficie: basterà creare un nuovo raggio dalle informazioni del RaycastHit:

//vedi esempio sopra per la creazione del raggio e del RaycastHit
if (Physics.Raycast(ray, out hit, 100f))
{
    Ray reboundRay = new Ray(hit.point, hit.normal);
    //qui va effettuato un nuovo raycast con il nuovo raggio
}

Le proprietà lightmapCoord, textureCoord e textureCoord2 invece indicano le coordinate sulle rispettive texture (lightmap, diffuse principale e UV secondarie), ma attenzione: a differenza di proprietà come point e barycentricCoordinate, queste non sono Vector3 nello spazio 3D bensì Vector2 nello spazio delle coordinate UV, utili quindi per "stampare" un effetto grafico su una texture in corrispondenza del punto specifico in cui è avvenuta la collisione (come nel caso di una macchia di sangue o una bruciatura in caso di colpi d'arma).

Maschere di livello, le LayerMask

A volte, abbiamo molti Collider nella scena ma nell'effettuare un raycasting vorremmo che questo raggio urtasse solo con alcuni di essi. È il caso ad esempio di un gioco action in cui il personaggio può saltare. Per evitare che il giocatore possa saltare in aria premendo il tasto "Salto" più volte, potremmo fare un raycast verso il basso e controllare la distanza della collisione. Se è inferiore ad un valore minimo (tipo 0.1f) vuol dire che il personaggio è a terra e può saltare.

Chiaramente, servirà un raycasting solo con gli oggetti che fanno da pavimento, per evitare problemi e per rendere l'operazione più leggera (anche perché viene effettuata ogni frame!). Come facciamo a dire a Unity di effettuare il controllo solo su di questi?

La funzione Raycast accetta come parametro opzionale una layer mask. Una layer mask non è altro che un numero intero, nel quale ogni bit rappresenta un layer: se il bit è 1, il layer è incluso nella maschera, se è 0 viene escluso (o mascherato). Quando usiamo la maschera per effettuare il raycasting, Unity non tiene in considerazione tutti gli oggetti e relativi Collider che appartengono ai layer mascherati, semplificando così di gran lunga il calcolo.

Facciamo un esempio: vogliamo eseguire collisioni solo con il layer "Floor", che dobbiamo creare usando il menu Edit > Project Settings > Tags and Layers.

Inserendolo nella lista dei layer ora è l'ottavo (User Layer 8).

Sulla scena, assegniamo all'oggetto Floor (un oggetto qualsiasi che contenga tutti i pavimenti e relativi Collider) e relativi figli il layer Floor.

Ora, nell'effettuare il raycasting possiamo crea la maschera di livelli. Per fare ciò, utilizziamo l'operazione di bit shifting, ovvero l'operatore <<. Questo operatore consente di spostare i bit in un numero intero. Lo facciamo sul numero 1, e spostiamo il bit attivo alla posizione 8, ovvero quella del layer che ci interessa.

//Facciamo un bit shift e creiamo una maschera che includa solo il layer 8
int layerMask = 1 << 8;
//Ora effettuiamo il raycasting usando la maschera appena creata
RaycastHit hit;
Ray ray = new Ray(transform.position, -transform.up);
if (Physics.Raycast(ray, out hit, 0.1f, layerMask))
{
    Debug.Log("Il personaggio può saltare");
}
else
{
    Debug.Log("Il personaggio è in aria");
}

Così facendo, la maschera di livello ha permesso di effettuare un raycasting solo sugli oggetti che appartengono al layer 8 (ovvero "Floor"). Se il raggio (che è lungo 0.1f) non ha colpito niente, vuol dire che il personaggio è in aria e non può saltare.

Se questo metodo del bit shifting risultasse troppo complesso, si può usare un'altra via attraverso l'interfaccia di Unity. Dichiariando una variabile pubblica di tipo LayerMask, sarà possibile selezionare in Unity i livelli che ci interessano da un menu a tendina.

Ecco il codice dell'intero componente che si occupa del raycasting. Si noti la variabile pubblica layerMask, che apparirà nell'Inspector:

public class CollisionScript : MonoBehaviour
{
    public LayerMask layerMask;
    void Update ()
    {
        RaycastHit hit;
        Ray ray = new Ray(transform.position, -transform.up);
        if (Physics.Raycast(ray, out hit, 0.1f, layerMask))
        {
            Debug.Log("Il personaggio può saltare");
        }
    }
}

Nell'esempio sopra, la maschera di livello del raycasting viene presa dalla variabile di classe layerMask, che deve essere pubblica in modo da rivelare il menu a tendina in Unity. Ora non resta che andare in Unity e selezionare il livello su cui vogliamo testare il raggio nell'Inspector del componente appena creato:

Spuntando i livelli che ci interessano, possiamo creare maschere di livello complesse in maniera molto più semplice che facendo complicati bit shift.

Ti consigliamo anche