In Unity tutti gli oggetti che devono muoversi con criteri che "rispettino" le leggi della fisica devono possedere un componente Rigidbody.
Gli oggetti che posseggono solo un Collider vengono definiti 'collider statici' perché anche a seguito di un urto non si spostano. Questo va bene per piattaforme camminabili, pavimenti o muri, mentre per personaggi, automobili ed astronavi, l'aggiunta di un Rigidbody permette di ricevere urti da altri collider; e di muoversi a seguito di spinte reali e non modificando direttamente il Transform come abbiamo visto in lezioni precedenti.
Il Rigidbody dà all'oggetto anche altre proprietà, come la possibilità di seguire la gravità verso il basso, avere una frizione (attrito) nelle collisioni con gli altri oggetti, ed una resistenza al movimento (che in Unity si chiama drag) che può essere usata per molti scopi, fra cui per simulare il limite di velocità che gli oggetti hanno in situazioni reali di attrito con l'aria.
Nota: se la simulazione fisica risulta irreale e gli oggetti si muovono in maniera lenta, bisogna controllare che la scala di questi sia realistica: una macchina dovrà essere fra le 3 e le 4 unità di lunghezza, una sedia avrà una base di 1x1, e così via. Valori molto diversi portano a simulazioni imprecise e poco ottimizzate.
Muovere oggetti mediante la fisica
Una volta applicato un Rigidbody a un oggetto, quando si mette la scena in Play esso inizierà a cadere per effetto della gravità. Questo comportamento si può eliminare togliendo la spunta alla casella Use Gravity nell'Inspector.
Perché l'oggetto si muova a seguito di un input, ci sono alcune funzioni della classe Rigidbody
che applicano una spinta all'oggetto. Quanto la spinta influenzi l'oggetto e di quanto lo smuova è una risultante di diversi fattori: la sua massa, il suo drag (questi due si trovano nell'Inspector fra le proprietà del Rigidbody), la sua frizione statica o dinamica (queste sono proprietà del Physic Material, che vedremo a breve).
La funzione più importante è AddForce. Questa accetta due parametri: uno fondamentale, che indica direzione e forza della spinta (un vettore forza), ed uno di tipo ForceMode che indica al motore fisico come trattare la spinta. Una spinta fisica infatti può essere:
- impulsiva (istantanea come un salto);
- continua (come un trascinamento, o il motore di un auto).
In più, può tenere conto della massa (per un risultato fisico più realistico), oppure no (se si vuole che la spinta abbia lo stesso effetto indipendentemente dal corpo a cui viene applicata).
La combinazione di questi due parametri crea quattro situazioni, identificate dai 4 valori che ForceMode può avere. Vediamo un esempio che ne contiene due:
private void Update()
{
int speed = 10;
int jumpForce = 100;
if(Input.GetKey(KeyCode.LeftArrow))
{
gameObject.rigidbody.AddForce(Vector3.left * speed, ForceMode.Force);
}
if(Input.GetKey(KeyCode.RightArrow))
{
gameObject.rigidbody.AddForce(Vector3.right * speed, ForceMode.Force);
}
if(Input.GetKeyDown(KeyCode.Space))
{
gameObject.rigidbody.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
}
In questo pezzo di codice, abbiamo implementato dei semplicissimi controlli per un personaggio basati sulla fisica.
Nel ciclo Update
vengono rilevati i tasti freccia sinistra e destra, usati per applicare una spinta continua di tipo ForceMode.Force (infatti usiamo GetKey
per rilevare la pressione continua del tasto). Per il salto, che è una spinta istantanea che deve solo alzare il personaggio da terra, usiamo GetKeyDown
per rilevare l'input e ForceMode.Impulse come tipo di forza.
Poiché una delle spinte è continua e l'altra dura solo un frame, devono essere di intensità differente (si notino in alto i due valori int speed
e jumpForce
per cui vengono moltiplicati i rispettivi vettori).
Considerazioni su Update e FixedUpdate
Nel nostro esempio abbiamo compiuto una scorrettezza progettuale: la fisica viene calcolata ad intervalli regolari nel FixedUpdate
e non quando avviene il rendering, nell'Update
.
Per questo motivo AddForce
(e tutte le funzioni simili) va utilizzato nel FixedUpdate
quando lo utilizziamo con i due ForceMode continuativi (Force
e Acceleration
).
L'input però va letto nell'Update. Vediamo l'intero script, rifinito:
public class MoveCharacter : MonoBehaviour
{
private int leftRightInput = 0;
private int speed = 10;
private int jumpForce = 100;
private void Update()
{
if(Input.GetKey(KeyCode.LeftArrow))
{
leftRightInput = -1;
}
else if(Input.GetKey(KeyCode.RightArrow))
{
leftRightInput = -1;
}
else
{
leftRightInput = 0;
}
if(Input.GetKeyDown(KeyCode.Space))
{
gameObject.rigidbody.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
}
private void FixedUpdate()
{
if(leftRightInput != 0)
{
gameObject.rigidbody.AddForce(Vector3.right * speed * leftRightInput, ForceMode.Force);
}
}
}
Nell'esempio sopra, rileviamo l'input nell'Update
, e lo applichiamo subito al corpo nel caso del salto (perché è in ForceMode.Impulse, che è immediato). Nel caso del movimento laterale, registriamo l'input in una variabile di classe int leftRightInput
, poi nel FixedUpdate
applichiamo una forza continua a destra o a sinistra in base al valore di questa variabile - solo se nel precedente Update è stato premuto uno dei tasti (quindi leftRightInput
non è 0
).
Rotazioni in fisica
Allo stesso modo in cui abbiamo ruotato gli oggetti mediante Transform.Rotate
, anche i Rigidbody permettono di operare sulle rotazioni. Così come AddForce, anche la funzione AddTorque applica una spinta (rotazionale in questo caso) che influenza il corpo in base alle caratteristiche che questo ha (massa, baricentro, ecc.).
Ad esempio, supponendo di aver costruito un veicolo a ruote con un gameObject con quattro collider a forma di cilindro, possiamo applicare una torsione alla pressione della freccia in su:
private GameObject wheel1, wheel2, wheel3, wheel4;
private bool goForward;
private int speed = 10;
private void Update()
{
goForward = Input.GetKey(KeyCode.UpArrow);
}
private void FixedUpdate()
{
if(goForward)
{
wheel1.rigidbody.AddTorque(transform.right * speed, ForceMode.Force);
wheel2.rigidbody.AddTorque(transform.right * speed, ForceMode.Force);
wheel3.rigidbody.AddTorque(transform.right * speed, ForceMode.Force);
wheel4.rigidbody.AddTorque(transform.right * speed, ForceMode.Force);
}
}
L'input del tasto freccia viene conservato in una variabile bool goForward
, ed in base a questo nel FixedUpdate
viene applicato un momento (torque) sull'asse X del corpo, per far andare avanti il veicolo usando la frizione che le ruote hanno con il suolo.