Guida a Unity 3D -...

147
0 Guida a Unity 3D Scritta da: http://www.html.it/ Editata da: http://wasdprogrammer.altervista.org/ Camillo Quattrocchi

Transcript of Guida a Unity 3D -...

Page 1: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

0

Guida a Unity 3D

Scritta da: http://www.html.it/

Editata da: http://wasdprogrammer.altervista.org/ Camillo Quattrocchi

Page 2: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

1

INDICE: 1.Unity, introduzione e filosofia…………………………………………………………02

2.Interfaccia, pannelli e gestione dei progetti………………………………………...03

3.Scene view e navigazione nella scena……………………………………………… 08

4.Trasformazioni……………………………………………………………………………10

5.Gerarchie parent-children in Unity……………………………………………………15

6.GameObject e componenti……………………………………………………………..18

7.La Game view……………………………………………………………………………..22

8.Prefab e instanze in Unity………………………………………………………………25

9.Prefab composti da più GameObject…………………………………………………28

10.Build: compilare il gioco………………………………………………………………33

11.Introdizione allo scripting…………………………………………………………….38

12.MonoBehaviour, gli eventi di Unity…………………………………………………40

13.Operazioni sui componenti…………………………………………………………..44

14.Prefab. istanziare molti oggetti a runtime…………………………………………46

15.Vector3, i vettori posizione e movimento………………………………………….50

16.Transform: spostare oggetti e gruppi di oggetti…………………………………52

17.Transform: rotazione, orientamento e scalatura………………………………….53

18.Raycasting: non solo traiettorie degli spari……………………………………….55

19.Gestione dell’input, tastiera e joypad………………………………………………60

20.L’input da mouse, touch e sensori………………………………………………….64

21.Collider, gestire le collisioni in Unity……………………………………………….69

22.Rigidbody, simulazioni fisiche con Unity………………………………………….74

23.Physic Material e vincoli per oggetti “fisici”……………………………………….77

24.I joint……………………………………………………………………………………….79

25.Mesh 3D, gestire oggetti e modelli…………………………………………………..86

26.Materiali e shader……………………………………………………………………….94

27.Texture……………………………………………………………………………………98

28.Luci e ombre in Unity………………………………………………………………….109

29.Baking e Lightmaps……………………………………………………………………117

30.Light Probe, illuminazione global e movimento………………………………….126

31.Sistemi particellari……………………………………………………………………..131

32.Gestire l’audio, Source e Listener…………………………………………………..143

Page 3: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

2

1.Unity: introduzione e filosofia Unity3D (o semplicemente Unity) è un engine per lo sviluppo di videogiochi che dal 2005 (anno del suo primo rilascio) è diventato sempre più popolare, soprattutto fra gli sviluppatori mobile, fino a potersi definire l’engine per videogames più usato al mondo.

La filosofia di Unity

Non sono un mistero le ragioni per cui Unity ha riscosso così tanto successo in particolare tra gli sviluppatori indipendenti e mobile, i fattori sono principalmente due: la sua flessibilità ed il modello di business molto aggressivo e con un prezzo abbordabile.

Unity infatti non si classifica al primo posto per performance o qualità del rendering, ma è indubbiamente un software flessibile che permette di realizzare qualunque genere di giococon poco sforzo.

I principali concorrenti di alto livello, UDK, Cry Engine, o simili, spesso sono molto orientati verso un genere specifico (spesso gli FPS, o i giochi d’azione in terza persona stile Uncharted), rendendoli ottimi engine nel caso si sappia già il genere di gioco, ma non la prima scelta per un piccolo studio che deve decidere su quale tecnologia investire.

Inoltre, negli anni Unity ha aggiunto la possibilità di esportare per molte piattaforme , fra cui tutti i sistemi operativi desktop, quasi tutti quelli mobile (iOS, Android, Blackberry, a breve anche Windows 8), per web (con un apposito plugin, come Flash) e per le principali console (Wii, PS3, Xbox, ed a breve quelle di nuova generazione). Questo ne fa una scelta formidabile per i team che hanno intenzione di portare il loro gioco su più piattaforme.

Unity è disponibile in due versioni (Free e Pro) e con diversi plugin che possono essere acquistati in maniera indipendente, per adattarsi alle necessità ma soprattutto alle tasche di tutti. Nel corso di questa guida utilizzeremo la versione Free (scaricabile da questo link), che a dispetto del nome contiene una gran quantità di funzioni, e permette di realizzare un gioco completo e addirittura pronto ad essere messo in vendita.

Indice della guida e requisiti

La guida sarà suddivisa in capitoli, ognuno riguardante una delle macro-aree del programma:

1. Interfaccia dell’editor: impareremo a navigare dentro Unity e a gestire progetti e i relativi asset;

2. Programmazione:tutti i concetti di base della programmazione in Unity, utilizzando C# come

linguaggio;

3. Grafica: spiegazioni dei materiali, del rendering, delle luci, delle texture;

4. Fisica: le simulazioni fisiche, come gestirle e come regolarle;

5. Animazione: vedremo come aggiungere animazioni ad oggetti e personaggi;

6. Audio: vedremo come aggiungere effetti sonori e musica al progetto.

Page 4: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

3

Per seguire la guida, servirà un computer PC o Mac (Unity non è disponibile per Linux) ed un software di elaborazione di immagini (per la parte di Grafica).

Istallazione del software

Unity è disponibile dalla pagina di download sul sito di Unity Technologies. Per scaricarlo non dovete per forza registrarvi al network degli sviluppatori, ma può essere utile farlo in caso vogliate chiedere aiuto nei forum. Questo account verrà riutilizzato nel caso decidiate, in un secondo momento, di comprare Unity Pro o uno degli add-on.

Una volta completato il download, usate l’installer contenuto all’interno del pacchetto. A fine installazione, cliccate sull’eseguibile di Unity e scegliete di utilizzare la versione Free.

Figura 1. Attivare la lincenza gratuita di Unity

D’ora in poi, potrete gestire la licenza di Unity dalla voce in alto Unity > Manage License...(su Mac).

Insieme a Unity viene anche installato MonoDevelop, software che utilizzeremo per editare gli script nel capitolo relativo alla programmazione. In ogni caso, sentitevi liberi di utilizzare qualunque IDE di programmazione vi sia più comodo, da SublimeText a Visual Studio (solo su Windows).

Page 5: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

4

2.Interfaccia, pannelli e gestione dei progetti In questa lezione, vedremo come funziona l’interfaccia di Unity, ed impareremo come creare un progetto.

Creare un progetto

È molto probabile che, scaricando una nuova installazione di Unity, alla prima apertura appaia il progetto d’esempio, Angry Bots. Per semplificarci la vita, prima di fare qualunque operazione creiamo un nuovo progetto, in modo da avere l’interfaccia sgombra da distrazioni.

Basta cliccare su File > New Project... e verrà presentata una finestra per scegliere la location del nuovo progetto.

Figura 02. Selezionare path e pacchetti del progetto

La lista di package in basso indica dei pacchetti già fatti che possono essere importati in fase di creazione del progetto, ma al momento non sono necessari. In ogni caso, sarà possibile importarli in un secondo momento andando su Assets > Import Package.

All’atto di salvare un nuovo progetto, Unity crea una cartella che sarà la base di tutto il progetto e conterrà tutti i contenuti prodotti da voi: gli asset, gli script, le texture, e i metadati che li legano insieme. Se doveste passare il progetto a qualcuno, dovete condividere sempre l’intera cartella.

L’interfaccia di Unity e i pannelli

Ecco l’interfaccia che verrà mostrata alla creazione di un nuovo progetto:

Page 6: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

5

Figura 03. I pannelli principali di Unity (click per ingrandire)

Come si può notare, l’interfaccia di Unity è tutta formata da pannelli, i quali possono essere posizionati a piacimento trascinando le linguette che ne mostrano il nome.

Ad esempio, provate a trascinare la linguetta Game a fianco del pannello Inspector: la finestra Scene si dividerà in due, facendo spazio per il pannello Game. Inoltre, si può ridimensionare la finestra Game trascinandone il bordo, per renderla simile a quella Scene come larghezza.

Questa è una configurazione ideale se si vogliono piazzare elementi in scena, ma allo stesso tempo vedere in tempo reale come verranno nel gioco. Ecco come risultano i due pannelli ora:

Figura 04. Configurazione Game e Scene (click per ingrandire)

Page 7: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

6

Allo stesso modo si possono ridimensionare tutti gli altri pannelli, aprirne di nuovi (dal menuWindow), o chiuderli (tramite l’iconcina a destra del nome, come nella figura sotto).

Figura 05. Aggiungere pannelli

In alto a destra è presente inoltre un menu a tendina che permette di ritornare al layout di Default, caricarne altri predefiniti (2 by 3, 4 split, Tall e Wide) o di salvare quello attuale.

Un altra funzione utile è che si può ingrandire qualunque pannello a tutto schermo cliccando col tasto destro sulla tab del nome e selezionando Maximize, o premendo la barra spaziatrice mentre il mouse è in qualunque punto del pannello (questo non funziona in caso il gioco sia in esecuzione, perché la barra spazio viene presa come input del gioco stesso) oppure, se avete un laptop che supporta il multitouch, facendo un gesto di pinch “ad aprire” sul trackpad (e viceversa per riportarlo alla dimensione originale).

Il pannello Game ha anche un tasto Maximize on Play, che se attivato ingrandisce il pannello Game quando si avvia il gioco e lo riduce quando torna in pausa, per avere una visione perfetta di cosa succede durante l’esecuzione.

Page 8: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

7

Salvataggio e gestione del progetto

Pur fornendo nel menu in alto la voce File > Save Project, Unity salva il progetto in automatico ogni pochi minuti, e alla chiusura. Per questo motivo non è necessario salvare il progetto, sarà invece fondamentale salvare le scene che compongono il gioco.

Il pannello Project visualizza tutti i file (i cosiddetti “assets”) presenti nel gioco. All’inizio è vuoto perché non ci sono asset, ma è possibile creare nuovi asset e cartelle direttamente da Unity. È buona pratica infatti, come primo passo, iniziare a creare le cartelle che serviranno per ordinare gli asset per categoria, ed inserirli nella cartella giusta man mano che vengono creati.

Per creare una cartella, basta fare click destro nel pannello Project e selezionare Create..., o anche usare l’apposito tasto nel pannello o la voce di menu Assets > Create. Nell’immagine in basso, vediamo un progetto con alcune cartelle già create:

Figura 06. Gli asset del progetto

Questo pannello mostra esattamente il contenuto della cartella Assets in Esplora Risorse o Finder, a partire dalla cartella Assets:

Figura 07. Cartella Assets

È molto importante che tutte le operazioni di spostamento di file e cartelle siano fatte dentro il pannello Project di Unity e non manualmente dentro Esplora Risorse o Finder, perché Unity mantiene una serie di metadati che rappresentano i collegamenti fra tutti gli assets.

Spostando i file in Esplora Risorse o Finder questi collegamenti si romperebbero, perdendo tutti i riferimenti fra materiali, texture ed oggetti vari.

È consentito invece inserire oggetti nelle cartelle, ed appena si torna su Unity questi li importerà anche nel progetto.

Page 9: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

8

3.Scene View e navigazione nella

scena Continuando la serie sull’interfaccia di Unity, in questa lezione vedremo come navigare nella scena mediante la Scene View, e quali sono i principali controlli per muoversi tra gli oggetti in essa contenuti.

La Scene View

Il cuore di Unity, la Scene View è la rappresentazione dell’ambiente di gioco e visualizza gli oggetti di gioco in maniera simile ai programmi di modellazione 3D come 3D Studio Max o Blender. Ecco come si presenta con alcuni semplici oggetti in scena:

Figura 8. La Scene View

La Scene View contiene molto di più dei soli oggetti “tangibili” presenti in scena. Come si può vedere dall’immagine, al centro del mondo di gioco viene visualizzata una griglia, che permette di orientarsi nello spazio 3D e di capire qual è il piano di base.

Inoltre, tutti gli oggetti che non hanno una rappresentazione fisica (come le luci, i suoni, gli emettitori dei sistemi particellari) vengono visualizzati tramite icone (i cosiddetti Gizmo) che possono essere nascoste o ridimensionate attraverso il menu dropdown Gizmos presente nella barra sopra la finestra della vista stessa.

Figura 9. Gizmos (click per ingrandire)

Page 10: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

9

Navigare nella scena

Per navigare nella scena, muovendo quindi il proprio punto di vista, sono disponibili 3 comandi principali: pan, zoom, e orbit.

Il pan (l’icona è una manina) si può fare cliccando e trascinando con il tasto centrale del

mouse, oppure premendo Q e poi trascinando col tasto sinistro. Questo muoverà la

telecamera nelle quattro direzioni, su un piano immaginario parallelo allo schermo del

computer.

Lo zoom viene effettuato con la rotellina del mouse, oppure tenendo premuto Ctrl + Alt e

trascinando con il tasto sinistro in alto e in basso. Più che uno zoom, corrisponde ad una

carrellata in avanti della telecamera.

L’orbit, familiare a chi lavora con il 3D, è un movimento orbitale della telecamera che punta ad

un punto immaginario. Per effettuare l’orbit è necessario premere Alt (apparirà un puntatore a

forma di occhio) e trascinare con il tasto sinistro del mouse.

Il punto centrale dell’orbit non è visibile, e si muove quando viene fatto il pan (non durante lo zoom). Per posizionare questo punto e centrare la vista su un oggetto, si può premere F dopo aver selezionato un oggetto nella scena o nel pannello Hierarchy.

Altro modo utile di navigare la scena è alla maniera dei First Person Shooter: tenendo premuto il tasto destro, apparirà un’icona di un occhio con a fianco i tasti direzionali. In questa modalità, trascinando il mouse (senza mollare il tasto destro) la camera ruoterà su se stessa invece che intorno ad un punto di vista (come appunto nei giochi FPS), mentre tramite tastiera ci si potrà muovere in avanti, all’indietro, e sui lati, permettendo di effettuare un tipicoflythrough, utile per navigare la scena dal punto di vista del giocatore.

Assi, prospettiva e isometria

Unity presenta nell’angolo in alto a destra un gizmo che raffigura i tre assi. Cliccando su uno dei 6 coni, la vista viene allineata in maniera perfetta a quell’asse, permettendo così di vedere e posizionare gli elementi in maniera più precisa.

Figura 10.

Page 11: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

10

Inoltre, cliccando sul testo in basso, Persp/Iso, si passa dalla visuale prospettica a quella isometrica. Un confronto delle due:

Figura 11.

Le differenze sono che nella vista prospettica gli oggetti si deformano secondo la prospettiva, seguendo l’ideale punto di fuga (nell’immagine è allineato con la griglia di base), mentre nella visuale isometrica gli oggetti della stessa dimensione appaiono uguali, che siano vicini o lontani.

Sebbene in questo momento siano due modalità di lavoro, come vedremo più avanti anche durante il gioco si possono usare i due tipi di visualizzazione (ad esempio la visuale isometrica è molto utile per giochi strategici alla Age of Empires).

Vediamo ora come trasformare gli oggetti sulla scena.

4.Trasformazioni Continuando il parallelo con i programmi 3D, Unity ha 3 strumenti disponibili per trasformare gli oggetti in scena: spostamento, rotazione, e scalatura. Questi tre strumenti sono selezionabili tramite il pannellino in alto a sinistra, o mediante shortcut da tastiera.

Figura 13. Le trasformazioni nel pannello strumenti

Andando nel dettaglio, gli strumenti sono:

Spostamento

Lo spostamento (accessibile premendo il bottone con la croce, o tramite il tasto W) permette di muovere gli oggetti lungo i 3 assi: X, Y e Z.

Figura 14. Gizmo dello spostamento

Page 12: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

11

1. Cliccando e trascinando una delle tre frecce si può muovere un oggetto su un solo asse

(l’asse si illumina di giallo mentre si trascina).

2. Trascinando su uno dei quadrati che si trovano fra le frecce, si può muovere un oggetto su un

piano. Ad esempio, trascinando il quadratino verde (quello che si trova fra le frecce blu (Z) e

rossa (X), l’oggetto si muove su un piano che contiene gli assi Z e X, ma non viene mosso nella

sua coordinata Y.

Rotazione

La rotazione (accessibile dall’icona con le due frecce, o tramite E) permette di ruotare gli oggetti rispetto al loro punto centrale. Il gizmo in questo caso è una sfera che ha tre cerchi che si intersecano fra loro, anche questi di tre colori che rappresentano le rotazioni sui tre assi.

Figura 15. Gizmo della rotazione

Page 13: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

12

1. Trascinando uno dei tre cerchi colorati ruota l’oggetto su un asse (rosso per la X, verde per la

Y, blu per la Z).

2. Cliccando e trascinando in un punto della sfera che non è uno di questi cerchi, si può ruotare

l’oggetto in maniera libera.

3. Cliccando invece sul cerchio esterno, l’oggetto viene ruotato su un asse che è un raggio che

parte dalla camera (quindi la rotazione dipende dal punto di vista in quel momento).

Scalatura

La scalatura (l’ultima icona nel pannellino, o tramite R) permette di ingrandire o rimpicciolire gli oggetti. Il gizmo è simile a quello di spostamento, con dei cubi invece delle punte delle frecce.

Figura 16. Gizmo per la scalatura

Page 14: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

13

1. Trascinando una delle tre linee colorate (o il cubetto all’estremità) si scala l’oggetto su un asse

solo.

2. Trascinando il cubo al centro, si scala uniformemente sui tre assi (consigliato per non

deformare l’oggetto).

Nota: se usate un MacBook Pro con tastiera italiana, c’è un bug noto che inverte i tasti W eZ, rendendo quantomeno scomodo usare le shortcut per la rotazione o per la navigazione flythrough , o anche l’input da tastiera quando si testa il gioco. Sfortunatamente, il bug si presenta anche quando il gioco viene compilato per il rilascio finale.

Trasformazioni locali e globali

Quando si sta trasformando un oggetto che è stato ruotato, è possibile farlo secondo l’orientamento globale o locale. Ogni oggetto ha infatti i suoi assi locali X, Y e Z, che all’inizio corrispondono con quelli del mondo ma che successivamente – quando viene ruotato – si sfasano rispetto a quelli originali. Per questo motivo è possibile trasformare gli oggetti secondo il sistema di riferimento più utile in quel momento, cliccando sull’opzione Local/Global nel pannellino in alto vicino ai tool.

Figura 17. Pulsante Global (Local)

Quando la trasformazione è in modalità Global, ogni spostamento, rotazione e scalatura avverrà lungo gli assi globali, che sono sempre gli stessi (chiaramente) per tutti gli oggetti in scena. Ecco un esempio di spostamento con gli assi Global:

Figura 18. Spostamento su assi Global

Page 15: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

14

Come si può vedere nell’immagine, gli assi corrispondono con l’orientamento della griglia (X eZ).

Quando invece l’opzione è su Local, gli assi dell’oggetto dipendono dalla sua rotazione, e le trasformazioni avverranno su questi assi ruotati. Ecco un esempio dello stesso spostamento di prima, ma con assi Local:

Figura 19. Spostamento su assi Local

Page 16: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

15

Gli assi sono cambiati, e come si può notare ora Z (quello blu) punta decisamente verso il basso.

Pivot point

Il pivot è un punto immaginario che viene usato come punto ideale per leggere la posizione di un oggetto nella scena 3D. In Unity però le trasformazioni di scalatura possono avvenire in due modi: rispetto al centro, o al pivot.

Questa opzione (situata a fianco del tasto Global/Local) è molto utile quando si lavora con modelli importati dall’esterno, dove il pivot potrebbe non coincidere con il centro del modello. Si pensi ad un modello di un essere umano: il centro (o centro geometrico) è situato nel suo addome, mentre il pivot potrebbe essere stato messo per comodità in basso, in mezzo ai suoi piedi.

Decidere se effettuare le rotazioni con punto centrale sul pivot o sul centro geometrico è fondamentale per velocizzare il lavoro.

5.Gerarchie parent-children in unity In questa lezione, vedremo come utilizzare il pannello Hierarchy, e i vantaggi di inserire oggetti all’interno di altri creando un rapporto parent-children.

Il pannello Hierarchy

Situato inizialmente alla sinistra della vista Scene, il pannello Hierarchy è fondamentale per la gestione della scena: in esso possiamo vedere tutti i nomi degli oggetti in scena organizzati in una lista, selezionarli, ed altre operazioni.

Page 17: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

16

Inizialmente in una scena vuota, il pannello contiene solo la Main Camera che difatti è l’unico oggetto in scena. Andando su GameObject > Create Other > Cube, vedremo comparire il nome del secondo oggetto appena creato.

Con un click si possono selezionare gli oggetti (vengono evidenziati in blu), anche utilizzando le classiche tecniche di selezione multipla dei file (trascinamento, Ctrl o Cmd + Click per aggiungere e rimuovere dalla selezione, o Shift + click per selezionarne una serie, e così via).

Un doppio click sull’oggetto ha l’effetto di spostare il punto di vista sull’oggetto (nella Scene View), che è esattamente come premere F mentre l’oggetto è selezionato.

Nota: quando si usa una shortcut in Unity, è importante dove si trova il cursore del mouse. Ad esempio, premere F produrrà un effetto solo se il cursore è nella vista Scene, altrimenti non avverrà nessuno zoom.

Una volta selezionato un oggetto, con Cmd + Backspace (su Mac) o Canc (su PC) lo si può cancellare (o ancora, mediante la voce di menu Edit > Delete), mentre con Invio o un click (Mac) o F2 (PC), lo si può rinominare.

Parent e children

Abbiamo visto un esempio di Hierarchy molto semplice, ma in generale il numero di oggetti in scena può diventare elevato. Per organizzare la scena in maniera più gestibile, esiste la possibilità di organizzare gli oggetti collegandoli fra loro o, in altre parole, mediante il rapporto ‘parent-children’.

Quest’operazione definisce un oggetto padre, che è l’oggetto che contiene gli altri, ed uno o più oggetti figlio (i cosiddetti children), ovvero gli oggetti contenuti nel primo.

Per creare la connessione fra due oggetti, basta che nel pannello Hierarchy venga trascinato l’oggetto che deve diventare Child sopra quello che sarà il Parent. Il cursore diventa una freccia ricurva, ad indicare che l’oggetto verrà imparentato con quello su cui si sta trascinando, che viene evidenziato in azzurro.

Ad esempio, se creiamo due cubi, li rinominiamo, e poi trasciniamo il cubo Child Cube sopra Parent Cube:

Figura 20. Trascinare l’oggetto figlio sul genitore

Il risultato è questo:

Figura 21. Oggetto figlio agganciato al genitore

Page 18: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

17

Ovvero, il ChildCube ora è agganciato al ParentCube. La conseguenza di questo collegamento è che l’oggetto figlio subisce molte delle trasformazioni effettuate sul padre. Alcune di queste proprietà saranno oggetto di lezioni successive, quindi ci limitiamo a menzionarle rapidamente:

quando un oggetto parent viene disattivato, anche tutti i figli diventano inattivi;

quando viene cancellato o distrutto da codice anche i figli scompaiono (queste valgono sia

quando viene fatto nell’interfaccia di Unity, sia via codice).

Il collegamento parent-children può avvenire anche su più livelli, ovvero un oggetto può essere parent di altri ma a sua volta figlio di un terzo, e così via. Per questo motivo, è sempre utile organizzare la propria scena in scatole cinesi, ed un buon trucco è di avere una serie di oggetti vuoti che fanno da parent per tutti gli oggetti di un certo tipo.

Ad esempio, in un tipico gioco action tutti gli oggetti che rimangono fermi (pezzi di scenario, il sole, ecc.) potrebbero essere children di un unico oggetto vuoto chiamato Static Objects, un altro oggetto vuoto Moving Objects potrebbe contenere il giocatore e tutti i nemici, e così via.

Così facendo in qualunque momento si potrebbero disabilitare tutti gli oggetti statici con un solo click, e riabilitarli altrettanto facilmente in un secondo momento.

Trasformazioni parent-children

La conseguenza più importante del rapporto parent-children è che quando il parent viene scalato, mosso o ruotato, i children vengono trasformati insieme a lui. Questo vuol dire che se un oggetto parent viene scalato del 200%, anche i suoi children vengono scalati del 200% rispetto alla scalatura che avevano al momento in cui sono stati collegati a lui.

Ad esempio, creiamo due cubi sulla scena (mediante GameObject > Create Other > Cube), e spostiamo uno dei due di poco in modo che non siano sovrapposti. Chiamiamo quello al centro della scena ParentCube, e quello spostato ChildCube.

Scaliamo il cubo figlio tramite lo strumento scalatura, fino a farlo diventare circa il doppio della sua dimensione. Ora, lo facciamo diventare child trascinando il suo nome su quello diParentCube nel pannello Hierarchy.

Ora scaliamo il ParentCube come abbiamo fatto per il primo cubo, a circa due volte la sua dimensione. Noterete che il ChildCube lo segue, diventando ancora più grande:

Figura 22. L’oggetto figlio segue le trasformazioni del padre

Inoltre, se selezionate i due cubi uno alla volta, potrete vedere che nel pannellino Inspector (che vedremo più avanti in dettaglio) il valore di Scale nella scheda Transform è più o meno 2sia per il ChildCube sia per il ParentCube.

Page 19: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

18

Però, visto il rapporto parent-child, anche se hanno lo stesso valore di scalatura il ChildCuberisulta sensibilmente più grande perché la sua scalatura è il 200% (sua) del 200% (il parent), quindi è in pratica scalato del 400%.

6.Ganeobject e componenti In questa lezione vedremo in dettaglio il pannello Inspector, che è il cuore di Unity ed uno dei pannelli che vi troverete ad usare più spesso.

Per parlare dell’Inspector, è necessario parlare dei componenti (o Components) e deiGameObjects, che sono la chiave della flessibilità di Unity.

I GameObject

Quasi tutto ciò che viene visualizzato a schermo in Unity è un GameObject. Ogni entità di un gioco (il protagonista, un nemico, una piattaforma su cui saltare, la videocamera che inquadra la scena stessa, o anche il testo che mostra il punteggio) è un GameObject, ed ha un nome, alcune proprietà di base, e può essere posizionato nello spazio 3D.

I GameObjects possono essere di tipo molto diverso: possono essere visibili o invisibili, emettere suoni, o essere interattivi. Tutte queste proprietà vengono definite dai componenti, che sono delle parti che possono essere aggiunte o rimosse dai GameObject a piacimento.

Creiamo un nuovo GameObject dal menu GameObject > Create Empty. Un elemento viene posto nella scena, e selezionato. Il pannello Inspector ci mostra le sue proprietà:

Figura 23. Proprietà del GameObject

Vediamo le parti dell’Inspector una per una, cominciando dalla fascetta delle proprietà di base.

In alto, c’è un campo di testo con il nome del GameObject, che può essere cambiato a piacimento. A destra, una casella di spunta di nome Static indica se l’oggetto è immobile ai fini di alcune ottimizzazioni (ne parleremo più avanti).

Attivare e disattivare gli oggetti

A sinistra del nome, una checkbox indica se l’oggetto è attivo o no. Gli oggetti disattivati sono invisibili nella Scene View, non vengono mostrati in gioco (Game View), ed i loro componenti non sono attivi (ovvero i sistemi particellari non emettono particelle, i suoni non vanno in play, ecc.). Inoltre, gli eventuali script creati da noi non vengono eseguiti, e (molto importante) gli altri script non sono capaci di ‘trovarli’ nella scena, a meno che non

Page 20: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

19

avessero già un riferimento all’oggetto disattivo prima che questo venisse disattivato (vedremo come trovare oggetti nella scena nelle lezioni dedicate allo scripting).

Sotto, si possono assegnare tag e definire il Layer in cui si trova l’oggetto. A sinistra si può scegliere un’icona speciale con cui visualizzare l’oggetto nella Scene View.

I Componenti ed i loro parametri

Sotto questa fascia, iniziano i componenti. Il primo è il Transform, che non può essere rimosso, ed infatti anche un oggetto vuoto nasce con questo componente.

Ogni componente ha una serie di parametri, il Transform ne possiede tre: posizione, rotazione e scalatura. Essendo dei valori Vector3 (cos’è un Vector3 lo vedremo), sono indicati da tre valori (uno per ogni asse) per un totale di 9.

Questi valori possono essere cambiati cliccando nel campo corrispondente, oppure cliccando e trascinando sul nome a fianco al campo stesso.

Spostando, ruotando o scalando l’oggetto nella Scene View (come abbiamo visto in una lezione precedente), si vedranno cambiare i valori nel componente Transform in tempo reale.

Tutti i componenti possono essere espansi o chiusi mediante il triangolino vicino al nome (utile se un GameObject ne ha tanti). Inoltre facendo click sull’icona del libro si apre la guida locale di Unity (quindi disponibile anche offline) riguardo quello specifico componente.

L’icona dell’ingranaggio permette di resettare i valori di un componente, di rimuoverlo, di spostarlo su e giù nella lista (solo per comodità, non ha nessun risvolto sul gioco stesso), e di copiarlo (Copy Component) e successivamente incollarlo su un altro GameObject (Paste Component As New), oppure di copiarne i valori ed incollarli in un altro componente dello stesso tipo (Paste Component Values).

Aggiungere e rimuovere Componenti

Vediamo un esempio di un oggetto più complesso. Selezioniamo la videocamera presente nella scena cliccando sul suo nome (Main Camera) nel pannello Hierarchy. Se non fosse presente, possiamo crearne una nuova da GameObject > Create Other > Camera. Una volta selezionata o creata, l’Inspector mostrerà i parametri dell’oggetto:

Figura 24. Parametri del GameObject

Page 21: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

20

Un oggetto videocamera di default ha un Transform, una Camera, un GUILayer, un Flare Layered un Audio Listener. Come detto, sono i componenti che fanno un oggetto. Un oggetto videocamera non è altro che un GameObject con un componente Camera, e rimuovendolo di fatto smette di essere una camera, così come è possibile creare una camera a partire da un oggetto vuoto e aggiungendo il componente Camera.

Per aggiungere componenti ci sono diversi metodi. Il più semplice è cliccare il bottone Add Component che si trova alla fine dell’Inspector dopo tutti i componenti già esistenti:

Figura 25. Ricerca tra i componenti

Page 22: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

21

Si aprirà un pannellino con una ricerca, in cui è possibile digitare il nome del componente da aggiungere. Nel caso di script, è anche possibile crearne di nuovi (New Script).

Un altro metodo di aggiunta è il menu Components, in cui c’è l’opzione Add… che rimanda al pannellino di prima, oppure una scelta ad albero (ad esempio Component > Effects > Particle System).

Come dicevamo all’inizio, i componenti possono essere aggiunti e rimossi a piacimento, creando infinite combinazioni di GameObject diversi. Inoltre, si possono estendere i componenti predefiniti di Unity attraverso l’uso dello scripting, infatti tutti gli script in Unity (che sia JavaScript, Boo o C#, i tre linguaggi supportati) sono di fatto dei componenti.

Possiamo quindi creare più oggetti di tipo simile utilizzando dei GameObject con gli stessi componenti, ma poi assegnare alle proprietà di questi dei valori diversi.

Un caso tipico potrebbe essere la gestione dei nemici in un gioco, che potrebbero avere gli stessi componenti: Mesh Renderer e Mesh Filter per renderli oggetti visibili, ed in più uno script creato da noi con alcune proprietà (l’energia, il danno che procurano al protagonista, la velocità, ecc.). Semplicemente variando i valori delle proprietà di questi componenti, si possono creare nemici diversi sia nell’aspetto che nella funzionalità.

Nello specifico, selezionando una mesh 3D nel componente Mesh Filter se ne modifica l’apparenza, mentre cambiando i valori di energia, danni e velocità nello script custom si possono creare nemici più o meno difficili da sconfiggere.

7.La Game View In questa lezione, vedremo come utilizzare la Game View per visualizzare un’anteprima di come verrà visualizzato il gioco ai giocatori, e cosa comporta mandare il gioco ‘in Play’ dentro Unity.

Page 23: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

22

In una precedente lezione abbiamo parlato della Scene View, che offre una visuale sulla scena 3D nella quale si svolge il gioco. Per visualizzare quello che i giocatori vedono durante il gioco, è disponibile la Game View, che mostra il punto di vista della videocameraattualmente attiva.

Figura 26. Game View semivuota con un semplice cubo

Esaminiamo le poche opzioni presenti.

Il menu contestuale in alto a sinistra permette di scegliere l’aspect ratio della Game View, per rendersi conto dei limiti di visuale che i giocatori avranno su un certo device piuttosto che un altro. Inizialmente c’è scritto Free Aspect, che indica che la Game View avrà esattamente le dimensioni che gli vogliamo dare.

Le altre opzioni disponibili dipendono dalla piattaforma su cui si sta lavorando: per Mac, PC e Linux, le scelte saranno 5:4, 4:3, 3:2, 16:9, 16:10, e così via. Per iOS, verranno presentate tutte le scelte utili: iPhone verticale, iPhone orizzontale, iPhone5, iPad, ecc.

È importante ricordare che Unity scala tutto in relazione all’altezza. Se un oggetto è in alto, rimarrà in alto indipendentemente dalla dimensione e dall’aspect ratio. Se è in basso, lo stesso.

Non funziona così invece il posizionamento orizzontale: un oggetto che è addossato al limite destro della visuale quando l’aspect ratio è 16:9, sarà fuori campo quando è in 4:3 (perché 4:3è "più stretto" di 16:9). Per questo motivo, il posizionamento di oggetti ai bordi dello schermo va gestito via programmazione.

Le altre opzioni sulla destra sono:

Maximize on Play, che ridimensiona la Game View portandola a pieno schermo quando il

gioco viene mandato in play,

Stats, che mostra statistiche utili sul gioco (anche quando non è in play!),

Gizmos, che se attivato permette di visualizzare i simboli dei componenti come Camera e

Particle Systems anche nella Game View oltre che nella Scene View.

Page 24: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

23

La Game View è una riproduzione fedele al 99% di quello che avverrà nel gioco. La differenza che potrebbe verificarsi a livello grafico dipende in generale dalla piattaforma di deploy: sviluppando un gioco per iOS su Mac, chiaramente le performance saranno diverse da quelle sul device stesso. Allo stesso modo, sarà impossibile creare un gioco che usa le DirectX11 su Mac.

Detto questo, la Game View è comunque una rappresentazione molto fedele del risultato finale.

Ecco ad esempio un confronto fra la Scene View e la Game View di un semplice gioco:

Figura 27. Confornto tra Scene View e Game View

Come si può notare, oltre alla differenza di angolo di visuale ed ai gizmo presenti nella Scene View, la resa grafica è identica.

Quello che viene visualizzato nella Game View è il punto di vista della videocamera attiva al momento. Se la scena contiene più di una videocamera, quella attiva sarà l’ultima creata, oppure si potrà scegliere via codice quale attivare in ogni momento (e magari alternarle).

Mandare il gioco in Play

Per provare il gioco, è possibile entrare nel cosiddetto Play Mode mediante il tasto Play in alto al centro.

Figura 28. Il bottone Play

Quando viene premuto Play (Cmd + P su Mac e Ctrl + P su Windows), Unity oscura leggermente tutta l’interfaccia, ed inizia ad eseguire il gioco. Questo vuol dire che tutti i

Page 25: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

24

componenti che hanno un comportamento nel tempo (come i sistemi particellari o i rigidbody, che fanno cadere gli oggetti con la gravità) verranno attivati ed andranno in play anch’essi. Inoltre, tutti gli script connessi a dei GameObject attivi verranno eseguiti.

In questo momento, vengono anche attivati i controlli del gioco (se presenti), ed anche tutte le shortcut (ad eccezione di quella per il Play, Pause, ecc.) vengono disattivate.

Tutti i pannelli di Unity, inclusa la Scene View, si comportano in maniera simile a quando il gioco è in stop. È possibile modificare i valori dei componenti mentre il gioco è in Play e addirittura trascinare oggetti dal pannello Project alla Hierarchy, o direttamente in scena (così come è possibile cancellarli, duplicarli, rinominarli, ecc.).

Allo stesso modo, si possono aggiungere o rimuovere componenti mentre il gioco è in esecuzione, creando la possibilità di testare in tempo reale situazioni che magari si verificano raramente o a caso (perché magari nel gioco c’è un intelligenza artificiale che non propone sempre le stesse situazioni).

Le funzioni Pause e Step

Una funzione fondamentale di Unity è la possibilità di mettere in pausa l’esecuzione del gioco premendo il tasto Pause (Ctrl + Shift + P), anche prima della pressione del Play, in modo che il gioco parta già in pausa.

Gli usi di questa funzione sono molteplici: di solito è utile mettere il gioco in pausa quando si vuole debuggare una scena in cui il ritmo di gioco è rapido. Premendo pausa, si può avere il tempo di analizzare la posizione dei gameObject, eventualmente ruotando la visuale nella Scene View, e di leggere tutti i parametri esposti dai vari componenti nell’Inspector, con la possibilità di modificarli con calma.

Ad esempio, per tarare la quantità di rimbalzo di un oggetto che cade su un piano, si potrebbe mettere il gioco in pausa per modificare la Bounciness di un materiale fisico, tirarlo su mediante il gizmo Spostamento, e poi togliere la pausa per vederlo cadere di nuovo, ripetendo l’operazione finché non si è soddisfatti.

Premere Pause ha un effetto leggermente diverso rispetto a modificare il TimeScale del gioco mettendolo a zero (una modifica che si può fare dal menu sotto Edit > Project Settings > Time, o anche da codice), perché mentre in quel caso il gioco è fermo ma attivo, in questo caso è completamente bloccato, quindi non riceve neanche input dall’utente o eventi di rete.

Per questo motivo in alcuni casi premendo Pause il gioco potrebbe saltare degli eventi di rete importanti, portando ad un crash o comunque ad un errore. A parte questi rarissimi casi, la possibilità di mettere il gioco in Pausa è fondamentale per poter analizzare gli oggetti in scena in giochi d’azione o simili.

Infine, il tasto Step (il terzo del pannellino in alto) permette di andare avanti nel gioco un frame alla volta. Questo permette di effettuare un debug accurato di tutte le funzioni che negli script vengono chiamate una volta per frame (chiamate Update, ma questo lo vedremo dopo), o di analizzare simulazioni fisiche ad ogni aggiornamento, per rilevare eventuali errori (si pensi ad un corpo che si muove a grandissima velocità, come un proiettile).

8.Prefab e Instanze in Unity In questa lezione analizzeremo i prefab, ovvero i ‘blocchi di costruzione’ di Unity, utili per riciclare oggetti che compaiono più e più volte in scena.

Page 26: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

25

I Prefab

I prefab (abbreviazione che sta per ‘prefabbricati‘) sono un tipo speciale di asset, che non viene importato ma creato dentro Unity. Sono essenzialmente dei GameObject che vengono salvati nel progetto (e non solo in una scena, come tutti gli altri), per poi essere riutilizzati in diverse situazioni. Chi ha familiarità con Flash può pensare ai prefab come agli oggetti di libreria.

Di fatto, i prefab sono utili ogni volta che un oggetto deve comparire più di una volta, e deve avere caratteristiche simili. Ogni volta che un prefab viene messo in scena, si dice che quella è un’istanza del prefab stesso.

Il rapporto fra le Istanze ed i Prefab

Ho scritto caratteristiche ‘simili’ e non ‘uguali’, perché un’utile proprietà dei prefab in Unity (ma anche quella che genera molta confusione se usata male) è che i prefab hanno delle caratteristiche proprie che vengono riportate nelle istanze, ma a discrezione dello sviluppatore alcune istanze potrebbero differire in una o più di queste caratteristiche, pur rimanendo legate al prefab ‘padre’. Vediamo un esempio pratico.

Create un cubo in una scena vuota (GameObject > Create Other > Cube). Questo è un semplice GameObject, e tutte le modifiche che possiamo fargli rimarranno solo su di lui.

Trascinate il cubo dal pannello Hierarchy a quello Project. Verrà creato un nuovo prefab a partire dal cubo in scena. Il cubo nella scena ora è un’istanza di questo prefab, ed infatti il suo nome nella Hierarchy ora appare in blu.

È importante capire che tutte le modifiche apportate al prefab nel pannello Project, vengono applicate a tutte le istanze del prefab in tutte le scene del gioco, a meno che la proprietà modificata non sia stata già modificata nell’istanza stessa, cosa che la rende indipendente dal prefab.

Rinominate il primo cubo in scena in Cube1, duplicatelo (Ctrl+D), rinominate il secondo comeCube2 e spostatelo un po’ in modo che non siano sovrapposti. Ora abbiamo due istanze del prefab Cube.

Selezionate il prefab Cube nel pannello Project (non le istanze nella Hierarchy!) e cambiate la scalatura Y da 1 a 4. Le due istanze in scena assumono scala 4, proprio come il prefab padre.

Figura 29. Due istanze del prefab Cube

Page 27: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

26

A questo punto, i due cubi sono identici al prefab. Selezionate Cube2 dalla Hierarchy o dalla Scene View, e cambiatene la scala Z a 3. Notate come il testo all’interno del campo nell’Inspector venga evidenziato in grassetto, a simboleggiare che ora questo valore è slegato da quello del prefab padre.

Figura 30. Proprietà dell’istanza

In realtà, è possibile notare come anche l’intera riga position (x, y, z) sia in grassetto. Questo perché nello spostare Cube2 subito dopo aver duplicato Cube1 abbiamo slegato la sua posizione da quella del prefab padre.

In ogni caso, è logico che posizione e rotazione delle istanze siano sempre slegati da quelle del prefab, altrimenti non si potrebbero comporre scene con oggetti che hanno tutti la stessa posizione!

A questo punto, la scalatura Z di Cube2 è slegata da quella del padre.

Modificate la scala del prefab dal pannello Project, portando tutti e tre i valori ad 1. I due cubi si abbassano, e Cube1 ridiventa un cubo perfetto. Cube2 però, si riduce in altezza, ma la sua scala Z (che ora è indipendente) rimane 3, rendendolo un parallelepipedo come nella figura seguente:

Figura 31. Istanza specializzata del Cube

Page 28: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

27

Il pannellino Prefab

Per dire all’istanza di seguire completamente il suo prefab padre, e quindi rendere anche la scala Z legata al padre, è possibile usare i tre tasti nel pannello Inspector, subito sotto il nome del gameObject:

Figura 32. Pannello Inspector

Select seleziona il prefab padre nel pannello Project.

Revert fa sì che l’istanza selezionata diventi di nuovo come il prefab padre in tutto e per tutto, perdendo tutte le modifiche locali a qualunque proprietà. Premete Revert e vedrete Cube2tornare ad essere un cubo, e il campo Z scale nell’Inspector non più in grassetto.

Per scoprire cosa fa Apply, modificate di nuovo la Z in scale a 3. Il cubo Cube2 è di nuovo largo, e la sua scalatura Z di nuovo custom rispetto al prefab a cui è legata.

Premete Apply. Vedrete che le modifiche specifiche di Cube2 vengono riportate sul prefabCube, che ora ha scalatura 3, ed a tutte le altre istanze (in questo caso Cube1). Apply quindi serve a modificare il prefab sulla base di modifiche che prima erano solo di una specifica istanza.

Come accennato prima, questa possibilità di avere un’istanza con solo alcuni parametri specifici e diversi rispetto al prefab è una caratteristica che dà molta libertà d’azione, ma che può confondere.

Si pensi ad un caso in cui in un gioco di simulazione in cui c’è una città con tante macchine rosse, tutte uguali come forma e tutte dello stesso colore, di cui quindi viene creato un prefab. In un secondo momento, serve una macchina di un colore bianco. Si potrebbe modificare solo il colore di una delle auto in una certa scena assegnandogli un materiale.

Di seguito, per motivi di gameplay si potrebbero cambiare le dimensioni delle auto modificando il prefab.

Ancora, si potrebbe cambiare il colore delle auto selezionando un’istanza e cambiando il materiale da rosso a blu, e premendo Apply. Il prefab verrebbe modificato con il nuovo materiale, ma l’auto bianca rimarrebbe invariata perché per quella specifica istanza il

Page 29: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

28

materiale è slegato dal prefab. Il problema nasce se, a seguito di una modifica sull’auto bianca, viene premuto Apply. Ora tutte le auto sono bianche perché è stato modificato il prefab a monte.

9.Prefab composti da più game

object I prefab possono contenere molto più che un singolo gameObject. È possibile infatti inserire più di un oggetto dentro un altro, e poi creare un prefab a partire dal parent. In questo modo, il prefab salva la gerarchia degli oggetti, che poi possono essere utilizzati tutti insieme.

Nel caso di prefab che contengono più oggetti, è importante capire dove si trova ilpivot dell’oggetto parent, per non avere sorprese successivamente.

In una scena vuota, create una capsula (GameObject > Create Other > Capsule), chiamatela Body, e posizionatela a (0, 0, 0). Create una sfera, rinominatela in Head, e tiratela su come se fosse la testa di un omino a (0, 1.5, 0).

Ora create un gameObject vuoto (GameObject > Create Empty), chiamatelo Human e posizionatelo in (1, 0, 0). Per farla diventare la base del nostro prefab (chiamataroot), inserite i due oggetti creati in precedenza all’interno di Human. La struttura dovrebbe presentarsi così:

Figura 33. GameObject annidati

Notate che, per avere il gizmo in quella posizione, bisogna che Pivot sia selezionato in alto.

Page 30: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

29

Create un prefab trascinando Human nel Project. Come potete vedere, il prefab creato mostra una piccola anteprima di tutti i suoi contenuti, ed un triangolino per espandere l’icona e vederli uno ad uno (se non la vedete usate lo slider in basso a destra nel pannello Project per controllare la dimensione delle icone):

Figura 34. Anteprima contenuti del prefab

Come dicevamo, è importante in fase di creazione di un prefab decidere dove mettere i pivot. In questo caso, se questo prefab fosse un personaggio umano, il pivot sarebbe in una posizione molto scomoda, perché Body e Head sono decentrati rispetto ad esso: provate a selezionarli nella Hierarchy, e noterete che hanno rispettivamente come coordinate relative (-1, 0, 0) e (-1, 1.5, 0). Questo creerebbe delle problematiche quando il personaggio si gira, perché ruoterebbe secondo un centro che non è posizionato al centro dei suoi “piedi”.

Per correggere questo difetto, riposizioniamo gli oggetti interni al prefab a partire dalla sua istanza nella scena. Selezionate Head e Body e portateli rispettivamente a (0, 2.5, 0) e (0, 1, 0). Selezionate Human, e vedrete che ora il pivot del padre è al centro in basso del personaggio, dove si trovano i suoi piedi.

Figura 35. Riposizionare il centro di rotazione

Page 31: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

30

Per completare l’operazione, premete Apply nell’Inspector. Il prefab è ora stato modificato, e tutte le nuove istanze avranno un pivot in basso al centro.

Nota: è consigliabile che la root di un prefab sia sempre un gameObject vuoto. In questo modo, per disabilitare interamente uno dei suoi oggetti (dall’editor ma anche da codice) basterà disabilitare l’oggetto figlio.

Si immagini ad esempio un prefab di una casa, in cui l’oggetto root contenga le mura ed il tetto, e i figli siano porte e finestre. Per rimuovere le mura ed il tetto temporaneamente, bisognerebbe creare uno script che cicla tutti i componenti dell’oggetto root (Renderer, Collider vari, ecc.), perché disabilitandolo verrebbero disabilitati anche i figli. Invece se l’oggetto root fosse un gameObject vuoto e mura e tetto fossero un semplice gameObject figlio, basterebbe disabilitare quello per ottenere l’effetto voluto.

Aggiunta di GameObject in un Prefab

Vediamo come Unity risponde all’aggiunta e alla rimozione di gameObject all’interno di un’istanza di un prefab.

Duplicate l’intero omino creato in precedenza, e rinominate le due istanze Human1 eHuman2. Ingrandite il Body di Human1 con scalatura (2, 1, 2), in modo da renderlo grasso:

Figura 36. Duplicare l’omino

Se premessimo Apply nel pannello prefab di Human1, vedremmo entrambi gli omini diventare “grassi”. Per questo motivo, lavoreremo su Human2 che ora è l’istanza più “pulita” (ovvero non ha modifiche rispetto al prefab).

Create una nuova sfera, chiamatela Hand, impostate la scalatura a (0.5, 0.5, 0.5), spostatela all’interno di Human2, e posizionatela in (0, 1, -0.75). Dovreste avere una situazione simile:

Figura 37. Aggiungere una sfera come manina

Page 32: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

31

Come si può notare, Hand è scritta in nero nel pannello Hierarchy, a simboleggiare che non fa parte del prefab. Premete Apply nell’Inspector, e vedrete tre modifiche: il nomeHand diventa anch’esso blu, appare Hand anche in Human1 come purenel pannello Project dentro al prefab Human.

A questo punto è possibile regolare la posizione della mano in Human1, perché essendo il suo Body più largo nasconde la mano. Basterà posizionarla in (0, 1, -1.25) per ottenere un risultato simile a Human2 (non premete Apply o questa modifica verrà propagata anche al prefab originale!).

Così facendo però, ci stiamo pericolosamente allontanando dal concetto di prefab: abbiamo in scena due istanze di un prefab, ma una di esse contiene già due modifiche custom. In una situazione di lavoro, bisognerebbe stare attenti a non premere Apply su questa, compromettendo così il prefab originale.

Rimozione di GameObject da un Prefab

Rimuoviamo ora le mani dagli omini. Selezionate Hand di Human2, ed andate su Edit > Delete (o con il tasto destro dalla Hierarchy, o con una shortcut…).

Unity vi presenta un avviso:

Figura 38. Avviso di “distacco” dal prefab

Page 33: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

32

Premete Continue. Come si può vedere, Human2 ha perso la mano, ma ora non è più legato al prefab originale, ed infatti tutti i nomi degli oggetti che lo compongono (Human2, Body, Head) sono in nero e non in blu nella Hierarchy. Qualunque modifica faremo al prefab d’ora in poi, non sarà più riportata in quest’istanza.

Per ricollegarlo al prefab, basterà premere Revert o Apply nell’Inspector.

Revert rende l’istanza come il prefab, quindi Human2 recupererà la sua mano.

Apply rende tutte le istanze ed il prefab stesso come Human2, quindi come risultato tutti

perderanno la mano.

Cancellare o scollegare un Prefab dalle sue Istanze

Ci sono due modi di scollegare un prefab dalle istanze, rendendole così indipendenti.

Selezionate Human1. Per renderlo un gameObject indipendente e non collegato ad alcun prefab, è sufficiente selezionare GameObject > Break Prefab Instance. Il suo nome in Hierarchy diventa nero, ed ora tutte le modifiche che gli faremo non verranno applicate alle altre istanze né al prefab.

La stessa cosa succederebbe se cancellassimo (per sbaglio o intenzionalmente) un prefab. Cancellate Human dal Project.

Human1, l’istanza rimasta in scena, ora ha un nome in rosso ad indicare che il suo prefab è mancante. In pratica si può considerarlo un oggetto indipendente, quindi si può selezionare come prima dal menu GameObject > Break Prefab Instance per renderlo davvero indipendente e rimettere il nome in nero.

La perdita di riferimenti a seguito della cancellazione di un prefab è una modifica chenon si può annullare, quindi bisogna fare molta attenzione nel cancellare prefab dal progetto, perché questo porta alla perdita di tutti i riferimenti al prefab in tutte le scene.

Prefab innestati

Nella versione di Unity attuale (4.2) è ancora impossibile creare un prefab dentro un altro prefab (ovvero i cosiddetti nested Prefabs, una caratteristica richiesta a gran voce da molti sviluppatori) senza perdere il riferimento al prefab padre.

Per spiegarlo meglio, facciamo un esempio pratico: immaginate di creare un prefab che è un edificio, completo di tanti gameObject interni (mura dell’edificio, una tenda da sole, una bandiera sul tetto, ecc.), chiamiato Building1. Immaginate di crearne altri due diversi, Building2, Building3.

Ora, volete creare un prefab per un isolato, in modo da creare un blocco di costruzione per un’intera città. Inserite Building1, Building2 e Building3 in un gameObject vuoto chiamato Block, e fin qui tutto ok: le istanze rimangono collegate ai 3 prefab in libreria.

Page 34: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

33

Se però trascinate questo gameObject nel pannello Project e ne fate un prefab, verrà creato un nuovo prefab Block ma le 3 istanze di Building1, Building2 e Building3perderanno il collegamento ai rispettivi prefab, diventando parte di Block. Questo vuol dire che qualunque modifica sui 3 prefab non si rispecchierà nelle copie contenute in Block, che ormai è un prefab separato ed indipendente.

In realtà, questo comportamento si può evitare con l’uso di diversi plugin scaricabili dall’Asset Store (alcuni dei quali a pagamento), ma spesso il processo è comunque macchinoso perché richiedono delle azioni da compiere ogni volta che si vuole creare un prefab innestato.

10.Build: Compilare il gioco In questa lezione vediamo come fare la cosiddetta ‘build’, ovvero come creare un pacchetto indipendente che i giocatori potranno scaricare (o comprare da uno store, a seconda della piattaforma).

La finestra Build

Il primo step per creare una build è aprire la finestra Build Settings. Si può accedervi daFile > Build Settings, o premendo Ctrl + Shift + B. La finestra si presenta così:

Figura 39. La finestra Build

Page 35: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

34

In alto, è presente una lista delle scene incluse nella build. Parleremo di questo fra poco.

In basso a sinistra, una lista espone tutte le piattaforme per cui è possibile creare una build. In generale, con la versione free di Unity sono presenti le tre piattaforme computer (PC, Mac e Linux), il Web Player e il Google Native Client, e i vari ambienti mobile (iOS, Android, BlackBerry).

Al momento (Unity 4.2), Windows 8 Apps e Windows Phone 8 sono disponibili solo per sviluppatori che ne hanno fatto richiesta specifica. Le piattaforme console (Xbox 360, PS3 e Wii) sono invece inaccessibili, a meno che non abbiate comprato una licenza specifica (il cui costo supera di gran lunga quello di Unity Pro).

La piattaforma di partenza è PC, Mac & Linux Standalone. Selezionandone un’altra, è sufficiente premere Switch Platform per lavorare sul gioco come se lo si volesse pubblicare su un’altra piattaforma. Nella pratica, questo implica un reimport (a volte anche lungo!) di tutti gli asset, perché Unity deve ricreare una cache con gli asset compressi per quella specifica piattaforma. Alcune cose potrebbero cambiare a livello grafico, visto che alcune piattaforme non supportano alcuni shader.

Inoltre, per ogni piattaforma si rendono disponibili alcune proprietà comuni ed altre specifiche in basso a destra nella finestra.

Development Build crea una build che mostra in basso a destra un’etichetta con questa scritta, ed abilita alcune opzioni per (appunto) build in via di sviluppo, come il profiler ed il debugging.

Page 36: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

35

Figura 40. Development Build

Vediamone alcune specifiche per piattaforma:

Web Player

Streamed crea una build i cui asset sono caricati in streaming, ovvero gli utenti quando entrano nella pagina che contiene il gioco possono iniziare a giocare quasi da subito, mentre in background vengono scaricati gli altri livelli.

Offline Deployment crea una build di dimensioni leggermente maggiori perché include delle librerie necessarie che altrimenti verrebbero scaricate da internet. Utile se avete intenzione di distribuire la build su un CD, anche se a quel punto conviene fare una versione Standalone.

Standalone

Architecture, come è evidente, permette di decidere per quale architettura fare una build. In generale se si crea un gioco Mac o Linux, non c’è motivo di non farla Universal, gli utenti apprezzeranno. Per il resto, spesso gli store accettano più versioni (32/64 bit) e lasciano scegliere all’utente quale scaricare.

iOS

Symlink Unity libraries esclude le librerie di Unity dal progetto Xcode che viene generato. Questo crea un progetto leggermente più leggero, ma che non potrà essere compilato in Xcode se l’utente non ha istallato anche Unity (perché le librerie devono essere recuperate da lì).

Android e BlackBerry

Texture Compression permette di comprimere le texture con diversi algoritmi per creare build ad-hoc per diversi processori, utili se si vogliono migliorare le prestazioni sui diversi modelli presenti in questi mercati molto frammentati.

Player Settings

Cliccando sull’apposito bottone (o dal menu Edit > Project Settings > Player) si possono configurare altre proprietà relative al player, spesso condivise fra le varie piattaforme.

Figura 41. Proprietà del plugin per visualizzare il gioco

Page 37: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

36

Le opzioni in alto sono comuni, e vanno dalla possibilità di aggiungere un’icona o un cursore per il mouse (che devono essere texture presenti nel progetto).

Le altre opzioni sono tante ed elaborate e vanno oltre lo scopo di questa guida. Quando vi troverete ad affrontare la pubblicazione per una certa piattaforma, il consiglio è di leggere in dettaglio il manuale per le descrizioni dei singoli settaggi della piattaforma in questione.

Scene incluse nella build

Non è raro creare degli asset che poi in seguito non vengono usati, o di creare diverse versioni di uno stesso gioco che usano asset differenti. Per questo motivo, prima di creare una build bisogna decidere cosa effettivamente ci finirà dentro, e questo viene fatto in diversi modi.

Innanzitutto per decidere quali scene del gioco utilizzare, bisogna aggiungerle nella finestra Build Settings che abbiamo visto prima. In alto c’è una lista delle scene, ed è sufficiente premere Add Current per aggiungere una scena alla build.

La prima scena nella lista è quella da cui parte il gioco, e l’ordine anche è importante nel caso di build per web dove gli asset vengono scaricati progressivamente (vedi il parametro Streamed più su).

Per rimuovere una scena dalla build, sarà sufficiente togliere la spunta oppure selezionarla e premere Delete. Per riordinarle, basta trascinarle come gli elementi di una lista.

Ecco un esempio di progetto più complesso, dove la scena Alpha è stata disattivata (non verrà inclusa nella build) ed il gioco parte da una scena che si chiamaInitialization:

Figura 42. Escludere una scena dalla build

Page 38: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

37

Il numeretto a destra indica l’ordine delle scene, ed è quello che si usa anche in programmazione per richiamare una scena nuova da uno script (anche se è possibile anche richiamarle per nome, più pratico).

Nota: Una scena non inclusa in una build non può essere caricata da uno script.

È importante sapere che quando si include una scena nella build, tutti i prefab, i materiali, gli shader, gli script, le texture ed i suoni inclusi o collegati in qualche modo alla scena vengono inclusi nella build, e ne contribuiscono al peso.

La cartella Resources

Oltre alle scene, è possibile definire che alcune cartelle vengano incluse nella build anche se al momento della creazione del pacchetto non venivano richiamate in alcuna scena. Questo è molto utile quando nel gioco vengono caricate texture o suoni di cui non si conosce il nome a priori, ma che viene generato via script.

Ad esempio, un personaggio potrebbe avere 10 diversi costumi con 10 diverse texture, nominate Clothes0, Clothes1, Clothes2, Clothes3, ecc.

Questi costumi verranno chiamati da uno script, e per far sì che vengano inclusi nella build devono trovarsi all’interno di una cartella speciale chiamata Resources. Questa cartella deve trovarsi nella root del progetto di Unity, ovvero subito all’interno della cartella Asset.

Così facendo, qualunque elemento presente in Resources verrà incluso nella buildanche se non viene usato, aumentandone il peso. Di contro, qualunque elemento al di fuori di Resources non può essere caricato via codice, ed ogni tentativo risulterà in un errore.

Creare la build

In ultimo, premendo su Build viene creato il pacchetto. In genere Unity chiede dove vogliamo salvare, il tipo di file creato poi dipende dalla piattaforma.

Ad esempio, su Mac viene creato un file .app che contiene tutto il gioco. Su Windows e Linux viene creato un eseguibile ed una cartella _Data, necessaria per far funzionare il gioco.

Per iOS ed Android vengono creati dei file di progetto (Xcode o apk) che non sono pronti alla distribuzione, ma che vanno ricompilati per le rispettive piattaforme tramite gli SDK specifici. Anche qui, ogni piattaforma ha le sue criticità e spiegare tutto in una lezione sarebbe impossibile.

Page 39: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

38

11.Introduzione allo scripring L’interfaccia grafica di Unity permette di comporre scene molto interessanti, ma senza programmazione non ci potrebbe essere interazione e quindi non potremmo realizzare giochi. In questa lezione, inizieremo a vedere come utilizzare i componenti script per creare interattività.

Programmare in Unity

Come già accennato, per aggiungere interattività in Unity bisogna creare deicomponenti personalizzati attraverso la creazione di script, e collegarli ai Game Object in scena, proprio come si fa con i componenti di base come il rigidbody, i collider, ecc.

Chiaramente è possibile creare combinazioni complesse di script che interagiscono fra loro, però alla base di tutto c’è sempre un Game Object in scena che chiama uno (o più) script.

Questi componenti custom si comportano per lo più come i componenti normali, quindi sarà possibile aggiungerli, rimuoverli, abilitarli, disabilitarli, e copiarli da un oggetto all’altro.

A differenza dei componenti custom però, questi script risiedono nel progetto come gli altri asset (texture, suoni, modelli, …) ed ognuno è rappresentato da un file (con estensione che dipende dal linguaggio usato: .cs, .js, .boo).

Unity prevede l’utilizzo di 3 linguaggi diversi: C#, Javascript, e Boo. Possiamo ottenere essenzialmente le stesse cose con tutti e tre i linguaggi finché si tratta di interagire con gli elementi di Unity (ovvero, se è possibile fare qualcosa specifico di Unity in uno dei tre sarà possibile anche negli altri due).

La differenza sta nello stile dei tre: se preferite avere un linguaggio più strongly typed a scapito di una verbosità leggermente maggiore, vi troverete bene con C#; se invece siete familiari con JavaScript, potreste usare quello.

Per questa guida il linguaggio prescelto è C#, perché è quello mediamente più usato in Unity e quello per cui si trovano più risorse.

Una rapida panoramica di programmazione

Se non siete familiari col concetto di programmazione ad oggetti, dovreste prima leggere una guida più approfondita sull’argomento per capire le basi, per poi comprendere meglio il funzionamente degli script in Unity.

In breve, gli script in Unity sono tutti classi che ereditano da Monobehavior. Unaclasse identifica una categoria di oggetti (fisici, ma anche virtuali) che hanno tutti le stesse proprietà.

Ogni classe ha una sua definizione, contenuta in un file di testo (nel nostro caso, file.cs), il quale contiene anche tutte le proprietà degli oggetti figli di questa classe. Allo stesso modo, nella definizione di classe saranno presenti tutte le funzioni (metodi) degli oggetti di quella specifica classe.

Inoltre, essendo tutti gli script figli di Monobehavior, esistono funzioni che Unity chiama per noi quando avvengono alcuni eventi. Queste funzioni permettono di definire un flusso di gioco e di rispondere a determinate azioni, come lo scontro fra due oggetti, la disattivazione di un oggetto, e così via. Vediamo un esempio pratico.

Quando successivamente parleremo di classe, intenderemo la classe generica che definisce le caratteristiche dei vari oggetti simili fra loro stabilendo quali siano le funzioni e le proprietà. Possiamo pensare definire funzioni e proprietà dette statiche, e possiamo

Page 40: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

39

pensare che riguardino la classe e non i singoli oggetti (cioè valgono al di là anche senza la creazione di oggetti specifici).

Quando diremo oggetto o istanza, vorrà dire che stiamo parlando del singolo oggetto figlio di quella classe, che ha le sue proprietà con valori personalizzati rispetto agli altri figli della stessa classe.

Creare il primo script

Apriamo una scena vuota in Unity. Creiamo un oggetto vuoto (GameObject > Create Empty) a cui attaccheremo lo script. Per creare uno script, è sufficiente cliccare Add Component nell’Inspector dell’oggetto appena creato, poi New Script, dargli un nome e scegliere un linguaggio. Verrà creato un nuovo script con il nome prescelto nella root del progetto.

In alternativa, è possibile creare lo script dal pannello Project mediante il tasto destro e poi Create > C# Script. Così facendo però, lo script verrà creato ma non agganciatoall’oggetto in scena, e quindi quando premeremo Play, naturalmente, Unity non lo eseguirà.

Se avete creato lo script dal pannello Project, selezionate l’oggetto in scena e trascinate lo script nell’Inspector. Oppure, nell’Inspector cliccate su Add Component e digitate il nome dello script appena creato.

Come consiglio generale, quando avete creato uno script e alla pressione del tasto Play non succede niente, controllate prima che lo script sia effettivamente attaccato almeno ad un gameObject.

Facciamo doppio click sullo script nell’Inspector o nel Project. Questo aprirà il programma predefinito per editare gli script, che di default è MonoDevelop. Una volta in MonoDevelop, vedremo una classe che sarà più o meno così:

using UnityEngine;

using System.Collections;

public class MyScript : MonoBehaviour {

// Use this for initialization

void Start () { }

// Update is called once per frame

void Update () { }

}

Come è possibile notare, quando si crea uno script Unity crea la struttura della classe, e aggiunge due funzioni predefinite: Start ed Update. Le vedremo in dettaglio più in là, per ora è sufficiente sapere che se attacchiamo questo script ad un oggetto e premiamo Play, Unity eseguirà all’inizio del gioco quello che c’è scritto fra le parentesi graffe del metodo Start.

Modifichiamo lo Start così:

void Start ()

{

Page 41: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

40

Debug.Log("Script attivo sull'oggetto " + gameObject.name);

}

Salviamo lo script, e torniamo a Unity. Unity rileverà la modifica allo script, lo potete notare perché ogni volta che si salva uno script compare una piccola rotellina di caricamento in basso a destra nella finestra principale (sotto l’Inspector). Apriamo il pannello Console (se non è visibile, basta andare su Window > Console o premere Ctrl + Shift + C, e poi premiamo Play.

La console visualizzerà il messaggio "Sono uno script attivo sull'oggetto GameObject"(dove GameObject sarà il nome che avete dato all’oggetto creato prima). Questo perché Unity esegue tutti gli script della scena, e in quello che abbiamo creato noi trova una funzione Start, che come comportamento predefinito viene eseguita all’inizio del ciclo di vita di un oggetto (ovvero, quando abbiamo premuto Play).

Vedremo più in dettaglio nella prossima lezione come funzionano i vari metodi predefiniti di Unity (Start, Update, ecc.), e per cosa usarli.

12.Monobehaviour, gli eventi di

unity Monobehaviour è la classe dalla quale ereditano tutti i componenti dei nostri giochi. È fondamentale parlare di questa classe, poiché ci permette di associare a ciascun oggetto sulla scena, alcuni comportamenti particolari al verificarsi di certe condizioni.

Le condizioni sono rappresentate dai metodi della classe Monobehaviour sotto forma di eventi, cui ogni oggetto può reagire.

Prendiamo ad esempio un evento come MonoBehaviour.OnCollisionEnter(): questo evento si verifica ogni volta che un oggetto entra in collisione e ogni volta che accade Unity richiama automaticamente il metodo OnCollisionEnter.

A questo punto perché il nostro oggetto reagisca a eventi come questo, bisognerà assegnare ad essi una funzione, nella quale avremo scritto il comportamento del nostro oggetto, ovvero le istruzioni necessarie a reagire all’evento.

Le funzioni che servono a gestire gli eventi tipicamente si chiamano event handler ma in Unity prendono il nome funzioni evento, per semplicità.

In questa lezione vedremo alcune delle funzioni evento della classe Monobehaviour, che ci permettono di controllare il flusso del gioco.

Utilizzare le funzioni evento

Come abbiamo detto, Monobehaviour espone una serie di funzioni evento che, se inserite in uno script, vengono chiamate automaticamente da Unity al verificarsi di determinate condizioni.

Ad ogni frame, Unity scorre tutti gli script attivi in scena, e se trova una di queste funzioni predefinite, la chiama (passando quindi il controllo alla funzione). Al termine dell’esecuzione, il controllo viene restituito a Unity.

Sarà necessario rispettare in modo preciso i nomi degli eventi, quando definiamo una funzione evento, altrimenti essa non sarà richiamata, anche se lo script non restituirà errori. Ad esempio, un metodo di nome Start sarà eseguito in automatico alla creazione di un

Page 42: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

41

GameObject, ma se noi definiamo Starting, otterremo una semplice funzione che sarà chiamata solo se saremo noi a farlo esplicitamente.

Purtroppo per queste funzioni non sempre abbiamo il supporto dell’autocompletamento dell’ambiente di sviluppo, quindi si puà sbagliare facilmente un nome, col rischio poi di perdere tempo a cercare di capire perché quella funzione non fa ciò che ci aspettiamo, quando in realtà non viene proprio chiamata perché Unity la vede come un’altra funzione.

Alcuni di questi metodi, possono opzionalmente venire chiamati con dei parametri. Ad esempio OnApplicationFocus viene chiamato sia quando il gioco riceve il focus (specialmente utile nel caso di giochi nel browser, per sapere quando l’utente ha cliccato nell’area del gioco) che quando lo perde (se l’utente digita Alt-Tab o riduce ad icona il gioco, ad esempio). In questo caso, basta dichiarare il metodo così:

void OnApplicationFocus(bool focus){

}

In questo modo, la variabile booleana focus ci permette di sapere se il focus è stato guadagnato o perso, e di agire di conseguenza.

Forzare le funzioni evento

Questi metodi sono definiti con visibilità private per default, tuttavia è possibile anche dichiararli public. Così facendo, possiamo richiamarli comodamente da altri script, forzando un comportamento anche se non si verifica una condizione di gioco. Dichiararli public non influisce sul normale comportamento, quindi Unity continuerà anche a richiamarli in maniera predefinita (quindi è possibile che in un certo frame uno di questi metodi venga chiamato due volte, una da noi ed una da Unity).

Le funzioni Awake e Start

I metodi Awake e Start vengono chiamati una sola volta all’inizio del ciclo di vita di un oggetto. La differenza fra i due sta nel fatto che Awake viene chiamato molto presto, durante la preparazione della scena. In quel momento gli oggetti in scena non sono completamente valorizzati, quindi in Awake è sconsigliato svolgere operazioni che richiedono i valori di altri oggetti, come ad esempio leggere il tag di un altro oggetto in scena.

Start è utile per eseguire operazioni di preparazione al gioco, come la creazione di array che verranno riempiti in seguito, o la ricerca di elementi nella scena di cui vogliamo salvare un riferimento per poi lavorarci più avanti durante l’esecuzione.

Le grosse differenze fra Awake e Start sono due:

1. Awake viene sempre chiamato prima di Start: se ad esempio in uno script su di un oggetto

inseriamo una funzione Awake ed in un altro una Start, saremo sicuri che quando Start verrà

chiamato l’Awake sarà già stato eseguito;

2. mentre Awake viene sempre eseguita al caricamento di un oggetto, Start viene eseguita

solo se lo script è attivo (mediante la checkbox nell’Inspector vicino al suo nome).

Per questo motivo, uno script con solo Awake e senza Start di solito non ha la checkbox vicino al nome, perché di fatto non esiste il concetto di attivo o disattivo: sia che fosse attivo che disattivo, Awake verrebbe eseguito comunque su di esso.

Attivare e disattivare l’oggetto su cui risiede lo script crea una serie di casi interessanti. Eccoli nel dettaglio:

Page 43: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

42

Stato

GameObject

Stato

script Conseguenza

attivo abilitato viene chiamato prima Awake, poi Start

attivo disabilitato viene chiamato solo Awake. Se successivamente l’oggetto viene

attivato, verrebbe chiamato anche Start (ma non di nuovo

Awake)

disattivo abilitato

o

disabilitato

né Awake né Start vengono chiamati. Se il gameObject viene

attivato, passiamo nelle condizioni precedenti.

In ogni caso, Unity non chiamerà mai Awake e Start più di una volta per uno nell’intero ciclo di vita di uno script.

Update

Il metodo Update viene chiamato ad ogni frame, il che lo rende dipendente dal framerate (vedi sotto) subito prima che venga eseguito il rendering.

Per questo motivo, spesso in Update si aggiorna la posizione degli oggetti che si muovono (mediante operazioni sui loro componenti Transform e/o Rigidbody), in modo che vengano renderizzati nella nuova posizione dando al giocatore l’illusione del movimento.

Nell’Update di solito si eseguono anche controlli sull’input del giocatore. Ad esempio, molte funzioni della classe Input sono pensate per leggere gli input del giocatore in quel dato frame.

È importante comprendere che tutto ciò che è contenuto nell’Update viene eseguito completamente nello spazio di un frame, prima che il giocatore possa vedere alcunché. Per questo motivo, non si può implementare ad esempio un fade con un semplice loop for nel quale ad ogni ciclo viene diminuita l’alpha dell’oggetto.

void Update()

{

for (float f = 1f; f <= 0; f -= 0.1f)

{

// Diminuisci l'alpha di un po'

}

Page 44: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

43

}

Il loop verrebbe eseguito interamente nello spazio di un frame, e solo dopo l’oggetto verrebbe renderizzato, quindi l’utente vedrebbe scomparire l’oggetto istantaneamente. Per ottenere l’effetto voluto nel tempo ci sono altri metodi (come le Coroutine) che vedremo più avanti.

Time.deltaTime

Il metodo Update viene usato in Unity per creare quello che viene definito game loop: una ripetizione ciclica di controlli e di operazioni, che avviene una volta per frame e che determina cosa avviene in scena. Questo introduce un concetto molto importante: in quasi tutti i giochi, il framerate non è fisso, ma variabile. Questo vuol dire che non sapremo mai quanti frame ci sono in un secondo prima che il gioco venga eseguito, e quindi tutte le operazioni che dipendono dal tempo vanno in qualche modo adattate al framerate attuale.

Per fare un esempio, se un oggetto si deve muovere di 10 unità in 10 secondi, non potremo dargli una velocità fissa ad ogni frame. Se così facessimo, ipotizzando un framerate di 50 frame al secondo, verrebbe:

10 unità / 5 secondi / 50 frame = 0,04 unità per frame

Se per qualche motivo il framerate dovesse calare durante quei cinque secondi, l’oggetto non riuscirebbe a spostarsi di 10 unità in quel tempo. Ad esempio, in un secondo da 30 fotogrammi, l’oggetto percorrerebbe solo 1,2 unità invece di due (perché 30 x 0,04 = 1,2), rimanendo indietro di 0,8 unità rispetto al nostro piano.

Per questo motivo, tutti gli spostamenti e le grandezze che devono essereindipendenti dal framerate, si moltiplicano per un valore che equivale al tempo trascorso dal frame precedente. Questo valore si può trovare in una variabile statica della classe Time, e si chiama Time.deltaTime. Questa quantità, se moltiplicata per il movimento, ci restituisce sempre lo stesso valore indipendentemente dal framerate.

Nell’esempio di prima, se volessimo muovere un oggetto di 10 unità in 5 secondi, basterebbe usare:

10 unità / 5 secondi x Time.deltaTime = 2 x Time.deltaTime = X

Quella X è la velocità in un dato frame. Supponendo un framerate di 50 frame per secondo, Time.deltaTime varrà 0,02, quindi X sarà 0,04. In un secondo l’oggetto si muoverebbe di 0,04 x 50 frame, quindi di 2 unità.

Se il framerate dovesse scendere anche a miseri 15 frame al secondo, Time.deltaTimevarrà circa 0,1335 che moltiplicato per 15 fa sempre 2 unità, dandoci quindi una velocità costante indipendente dal framerate.

La variabile deltaTime non è utile solo per gli spostamenti, ma per tutti quegli incrementi che dipendono dal tempo, e che devono rimanere costanti indipendentemente dal framerate del computer su cui gira il gioco.

FixedUpdate

Simile ad Update, FixedUpdate è una funzione che Unity chiama in automatico ad intervalli regolari che non tengono conto del frame rate (di default FixedUpdate viene chiamato ogni 0.02 secondi).

FixedUpdate viene chiamato subito prima di fare i calcoli relativi al motore fisico, e per questo motivo viene utilizzato principalmente per effettuare operazioni che riguardano la fisica, come applicare forze ai componenti rigidbody, o operare con icollider.

Page 45: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

44

Come è facile intuire, poiché Unity renderizza la vista meno frequentemente di quanto viene chiamato FixedUpdate, di solito in questa funzione non si effettua alcuna operazione che riguarda la grafica, come spostamento di transform, cambiamenti di colore, intensità delle luci, ecc. Fare queste operazioni nel FixedUpdate sarebbe uno spreco di risorse: il risultato sarebbe che verrebbero eseguite più volte (nelFixedUpdate) prima che la scena possa essere renderizzata, ma senza che il giocatore veda effettivamente alcun cambiamento.

Di contro, è utile effettuare calcoli riguardanti la fisica nel FixedUpdate perché in questo modo questi potranno essere quanto più precisi possibile. Un esempio classico è il moto dei proiettili: poiché questi di solito si muovono a grande velocità, è possibile che in un frame un proiettile sia da un lato di un muro, e nel frame successivo sia dall’altro, di fatto trapassandolo perché Unity non rileva nessuna collisione (collisione che sarebbe dovuta avvenire in un frame intermedio, che non c’è).

L’intervallo a cui FixedUpdate viene eseguito può essere cambiato in Edit > Project Settings > Time, e modificando il Fixed Timestep. Valori minori daranno più precisione alla simulazione fisica, ma caricheranno di più il processore che, su hardware meno potente, potrebbe non eseguire correttamente alcuni calcoli. Di contro, a volte una grande precisione non è necessaria, e si può quindi aumentare il timestep per sforzare di meno il processore.

Altre funzioni evento

Unity ci mette a disposizione molte altre funzioni che verranno chiamate in automatico allo scattare di diversi eventi, descriverle tutte qui sarebbe anche abbastanza inutile, visto che molte sono autoesplicative.

Una lista completa di tutti i metodi predefiniti di Unity è disponibile nella documentazione ufficiale alla pagina della classe Monobehaviour.

13.Operazioni sui componenti Oltre all’approccio visuale, che abbiamo esaminato introducendo GameObject e componenti, possiamo operare sui componenti attaccati ai gameObject anche da codice. Vediamo come.

Aggiungere componenti a runtime

Può capitare di voler aggiungere componenti a un gameObject a runtime, piuttosto che collegarli dentro l’editor. Tra i diversi motivi abbiamo che metodi come OnCollisionEntervengono sempre eseguiti al lancio del gioco, anche se il componente è disabilitato (togliendo la spunta alla casellina apposita). Per evitare che certi metodi vengano prima del tempo, l’unico modo è rimuovere o aggiungere il componente a runtime solo nel momento in cui diventa davvero necessario.

Per aggiungere un componente, basta utilizzare il metodo AddComponent. Questa funzione è un “template”: possiamo chiamarla in congiunzione col tipo del componente per fare in modo che ci restituisca un riferimento al componente appena creato, su cui operare subito o di cui possiamo salvare un riferimento.

Ad esempio, aggiungiamo un Rigidbody ad un cubo di nome cube1:

Rigidbody newRigidbody = cube1.AddComponent<Rigidbody>();

Page 46: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

45

newRigidbody.drag = 0.1f;

Quando questa riga di codice viene eseguita sarà aggiunto un Rigidbody al cubo e questo inizierà a cadere secondo la gravità e con un drag di 0.1, ed eventualmente a collidere con altri oggetti (ammesso che abbia un Collider attivo!).

Allo stesso modo è possibile usare la funzione AddComponent in maniera generica, così:

Rigidbody newRigidbody = (Rigidbody)

cube1.AddComponent(typeof(Rigidbody));

Chiaramente, come si vede nell’esempio, dovremo effettuare un cast per far capire a Unity che tipo di componente stiamo aggiungendo.

Accedere ai componenti di un oggetto

Una volta aggiunti (sia da editor che in programmazione), possiamo avere accesso a qualunque componente di un oggetto mediante la funzione GetComponent. Anche questa può essere tipizzata, in questo modo:

cube1.GetComponent<Collider>();

Rimuovere componenti

Così come è possibile aggiungere componenti, è possibile rimuoverli. Tuttavia, non esiste una funzione dedicata: si usa la funzione Destroy (utile anche per distruggere altri tipi d’oggetti). Due esempi:

Destroy(rigidbody);

//qui recuperiamo al volo un altro componente di tipo Player

Destroy(gameObject.GetComponent<Player>());

Abilitare e disabilitare componenti

Continuando il parallelo con l’editor, come possiamo abilitare e disabilitare componenti spuntando la casellina vicino al loro nome, così si può farlo in programmazione, semplicemente cambiando la proprietà enabled del componente (e i componenti possono anche disabilitarsi da soli):

rigidbody.enabled = false;

collider.enabled = true;

gameObject.GetComponent<Player>().enabled = false;

Nota: l’aggiunta di un componente fa scattare le funzioni Start ed Awake di quel componente, a meno che questo non venga disabilitato subito dopo la sua aggiunta, così:

OtherComponent n = gameObject.AddComponent<OtherComponent>();

n.enabled = false;

Page 47: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

46

14.Prefab, istanziare molti oggetti a

runtime Dopo aver visto come aggiungere e rimuovere componenti a runtime, in questa lezione vedremo come fare per creare e distruggere interi oggetti.

La cartella Resources

Come per i componenti, in Unity si può decidere di creare oggetti nell’editor (trascinandoli dal pannello project nella scena, o in Hierarchy), oppure di farlo in programmazione. Tante volte questa risulta l’unica opzione, come nel caso di proiettili, esplosioni, ed in generale di tutti quegli oggetti che vengono creati in maniera ripetuta.

Per istanziare un oggetto, la prima cosa da fare è crearlo come prefab.

Definire un oggetto come prefab

È molto importante capire questo meccanismo ma c’è anche una maniera più diretta di istanziare molti oggetti, come nemici o proiettili, senza ricorrere al meccanismo della cartella Resources:

1. Si dichiara una variabile public di tipo GameObject (es. public GameObject prefab;) all’interno dello script che utilizziamo come controller (nella Main Camera ad esempio):

public class controller : MonoBehaviour {

public GameObject prefab;

// ...

2. Vi si correla da pannello Inspector il prefab che vogliamo replicare:

Page 48: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

47

3. Poi da codice (es. nella funzione Start) possiamo istanziare l’oggetto, come vedremo anche più avanti:

public class controller : MonoBehaviour {

public GameObject prefab;

// Use this for initialization

void Start () {

newCube = (GameObject) Instantiate(prefab);

// ...

Troviamo un esempio in questo articolo.

L’uso di Resources

In questa lezione vogliamo poter definire dinamicamente un oggetto come prefab, per questo avremo bisogno di inserire i nostri prefab all’interno della cartella Resources(o in una sua sottocartella). Si tratta di una cartella speciale in cui Unity, grazie all’oggetto Resources, può andare alla ricerca di oggetti da trattare come prefab a runtime.

Questo metodo può essere vantaggioso se vogliamo controllare meglio l’allocazione e il rilascio della memoria, quindi vediamo come si procede a definire un prefab direttamente da codice con questa tecnica.

Creare un prefab

Creiamo un semplicissimo prefab partendo da un cubo. Andiamo su Game Object > Create Other > Cube. Una volta in scena, aggiungiamo un componente Rigidbody. Nel pannello Project, creiamo una nuova cartella di nome Resources (attenti a dare questo nome esatto).

Ora trasciniamo il cubo dalla Hierarchy al pannello Project per creare un prefab. Cancelliamo l’oggetto in scena, e rinominiamo il prefab in SimpleCube.

Ora creiamo un nuovo script (con un qualunque nome), ed assegnamolo come componente ad un qualunque oggetto presente in scena (magari alla Camera di default) in modo che venga eseguito. Nello script scriveremo, dentro il metodo Start:

GameObject prefab = Resources.Load<GameObject>("SimpleCube");

Ciò che abbiamo appena fatto è di caricare un prefab di nome SimpleCube dalla cartellaResources, mediante la funzione statica Resources.Load.

Questa funzione accetta un parametro, ovvero il path del prefab da caricare. In questo caso, poiché SimpleCube non è in una sottocartella, possiamo passare semplicemente il suo nome.

Inoltre, Resources.Load è una funzione template, quindi possiamo passare un tipo in modo

che lo script sappia già di che classe è l’asset caricato (in questo caso GameObject, ma poteva

essere Texture2D, Material, AudioClip, etc.).

Per sapere di più sull’uso di Resources ci si può riferire alle references ufficiali

Page 49: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

48

Istanziare di oggetti, la funzione Instantiate

Una volta caricato, possiamo istanziare il prefab. Utilizziamo la funzione Instantiate, che è una funzione statica della classe Object e che crea una nuova istanza in scena a partire da un prefab che gli passiamo (nel nostro caso, il GameObject caricato prima).

GameObject prefab = Resources.Load<GameObject>("SimpleCube");

GameObject newCube = (GameObject) Instantiate(prefab);

All’esecuzione di queste due righe, vedremo comparire in scena un oggetto cubo, nella stessa posizione in cui era quello da cui abbiamo fatto il prefab. Inoltre, poiché come il prefab ha un Rigidbody attaccato, lo vedremo cadere secondo la fisica.

Come è possibile notare, Instantiate non è una funzione template, quindi è necessario eseguire un cast (ovvero quello che facciamo con (GameObject)).

È logico che su questo nuovo gameObject appena creato, potremo operare come abbiamo fatto fin’ora: ad esempio, se vogliamo operare sui suoi componenti rigidbody ed un altro script creato da noi (che qui chiamiamo OtherComponent), potremo normalmente fare qualcosa del genere:

newCube.rigidbody.mass = 10f;

newCube.GetComponent<OtherComponent>().enabled = false;

Non ci preoccupiamo per ora di posizionare il nuovo oggetto nello spazio 3D, lo vedremo nella prossima lezione.

Trovare un oggetto nella scena

Prima di vedere come rimuovere un oggetto, dobbiamo avere il modo di trovarlo in scena. Per cercare un oggetto presente in scena a partire da un altro script, possiamo usare diversi metodi. Il più banale è usare la funzione statica GameObject.Find:

GameObject otherCube = (GameObject) GameObject.Find("newCube");

Come si può notare, la funzione Find accetta un parametro di tipo string che è il nome dell’oggetto da cercare. Ovviamente, in caso ci fosse più di un oggetto in scena con lo stesso nome, Unity non avrebbe modo di distinguere fra i due, quindi passerebbe un riferimento al primo trovato.

Inoltre, GameObject.Find è una funzione estremamente lenta: è consigliabile infatti non utilizzarla nell’Update perché per come è strutturata, deve scorrere tutta la gerarchia della scena e leggere i nomi di tutti i GameObject incontrati.

Nel caso in cui un riferimento ad un oggetto serva ad ogni frame, è consigliabile utilizzare GameObject.Find solo una volta nello Start e salvare un riferimento da usare nell’Update. Ad esempio:

private GameObject otherObject;

void Start () {

otherObject = (GameObject)GameObject.Find("AnotherCube");

}

Page 50: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

49

void Update () {

otherObject.GetComponent<OtherComponent>().counter++;

}

In questo caso, per accedere al componente OtherComponent abbiamo salvato un riferimento all’altro oggetto in una variabile privata di nome otherObject.

Rimuovere gli oggetti

In ultimo, è possibile rimuovere oggetti dalla scena utilizzando la funzione staticaDestroy (l’abbiamo usata nella lezione precedente per eliminare componenti). Ad esempio:

otherObject = (GameObject) GameObject.Find("AnotherCube");

Destroy(otherObject);

In questo caso, viene cercato un oggetto a partire dal suo nome e poi viene rimosso dalla scena, e con esso tutti i suoi eventuali oggetti figli e tutti i loro componenti.

Attenzione però: Destroy non ha effetto istantaneo. La rimozione dell’oggetto viene messa in programma, e portata a termine solo alla fine del frame corrente. Questo perché una rimozione immediata porterebbe a problemi in diversi casi, come ad esempio nella risoluzione della fisica.

Esiste anche la versione immediata di Destroy, di nome DestroyImmediate, ma va usata solo quando si sta programmando un plugin per Unity da utilizzare quando il gioco non è in Play. Usare DestroyImmediate mentre in un normale flusso di gioco può solo dare problemi, e può portare alla distruzione permanente di asset (ovvero gli oggetti vengono rimossi dal pannello Project, e dalle cartelle in cui si trovano!).

15.Vector3, i vettori posizione e

movimento In matematica, un vettore è un elemento di uno spazio vettoriale identificato da una sua grandezza dimensionale (modulo) una direzione e un verso. In Unity, lo spazio di lavoro è tridimensionale, per cui la maggior parte del tempo avremo a che fare con vettori a tre dimensioni, ovvero che contengono 3 valori.

I vettori tridimensionali possono venire usati per diversi scopi:

descrivere la posizione di un oggetto nello spazio 3D;

quantificare uno spostamento nello spazio;

esprimere la velocità che un oggetto ha in un dato momento;

la rotazione sui tre assi.

Unity mette a disposizione tre classi per lavorare con i vettori:

Page 51: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

50

Classe Descrizione

Vector3 utilissima, come abbiamo appena detto, per lavorare nello spazio 3D

Vector2 utile per “ragionare” solo in due dimensioni

Vector4 che per ovvi motivi non rappresenta un posizionamento nello spazio, ma si

usa per altri scopi

Naturalmente non esiste un Vector1, un “vettore monodimensionale”: è semplicemente uno scalare e si può rappresentare con una semplice variabile di tipo float.

Vector3 è dunque composto da tre componenti, tutte di tipo float: x, y, z. Vediamo cosa rappresentano in Unity.

Componente Descrizione

Y la Y rappresenta la direzione verticale ed è orientata verso l’alto (i valori

negativi indicano il verso opposto, in basso)

Z la Z è considerata “avanti” (o indietro)

X la X è la direzione orizzontale orientata verso “destra”.

Tutto ciò essere diverso su altri programmi, dove a volte la Z è considerata “su” (come in 3D Studio Max), quindi è necessario fare attenzione.

Per creare un Vector3, è sufficiente usare la keyword new come al solito, specificando i tre valori nell’ordine x, y, z. Omettendo la z, Unity assumerà che il valore sia 0:

Page 52: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

51

Vector3 movement = new Vector3(2f, 0f); // è lo stesso che se avessimo scritto new Vector3(2f, 0f, 0f);

In questo esempio, stiamo esprimendo un vettore che punta dall’origine degli assi ad un punto che si trova lungo l’asse X, distante due unità. Qual è la lunghezza di questo vettore? Facile: 2.

Vector3 position = new Vector3(2f, 3f, -5f);

Quest’altro vettore indica un punto che si trova 2 unità lungo l’asse X, 3 lungo l’asse Y, e a 5 unità lungo quello Z, ma in direzione opposta (notare il simbolo “meno”). La lunghezza di questo vettore è meno facile da intuire, ma si può lasciare che sia Unity a calcolarla accedendo alla proprietà magnitude:

Debug.Log(position.magnitude);

Altra proprietà utile della classe Vector3 è normalized, ovvero la possibilità di ottenere la versione “normalizzata” del vettore. Se immaginiamo il vettore come una freccia che punta dal centro degli assi ad un punto nello spazio, la sua versione normalizzata sarà semplicemente lo un vettore che punta nella stessa direzione, ma di lunghezza 1. Va da sé che la magnitudo dei vettori normalizzati sarà sempre 1:

Vector3 position = new Vector3(2f, 3f, -5f); Vector3 normalizedVersion = position.normalized; Debug.Log(normalizedVersion.magnitude); //la console stamperà 1

16.Transform, spostare oggetti e

gruppi di oggetti In questa lezione vedremo come operare sul componente che è forse il più importante di tutti, il Transform.

Possiamo manipolare il componente Transform esattamente come facciamo sulla sua versione grafica nella finestra Inspector dell’editor. Il componente racchiude in sé alcune informazioni molto importanti del GameObject a cui appartiene, tra cui:

position, la posizione del gameObject (espressa con un Vector3);

rotation, la rotazione (espressa come Quaternion);

scale, il fattore di scala (sempre Vector3).

Per accedere al componente transform di un oggetto, Unity mette a disposizione la shortcut transform, da usare come se fosse una proprietà del gameObject stesso, o degli script associati ad esso.

Una prima distinzione da fare (che altrimenti potrebbe portare a parecchi grattacapi) è che mentre nell’editor questi valori sono locali, relativi quindi all’oggetto che lo contiene, in programmazione sono di solito assoluti. Questo vuol dire che se prendiamo un oggetto A, in posizione (0, 0, 0), ci mettiamo dentro un oggetto B in posizione (0, 0, 0), e poi spostiamo A di due unità sulla X (quindi in 2, 0, 0), selezionando B nell’Inspector vedremo che è in posizione (0, 0, 0), ovvero la sua posizione relativa è nulla. Se però in programmazione stampiamo la sua posizione mediante:

Debug.Log(objectB.transform.position);

La console restituirà (2, 0, 0), ovvero la sua posizione assoluta nella scena, indipendentemente dal suo parent. Per leggere la posizione relativa (ovvero quella visualizzata nell’Inspector), basterà usare la proprietà localPosition:

Page 53: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

52

Debug.Log(objectB.transform.localPosition);

Muovere oggetti nella scena

Per spostare un oggetto, sarà sufficiente modificare la sua position o localPosition. È da notare però che Unity non ci fa modificare le singole componenti del Vector3 che contiene la position, quindi per muovere l’oggetto anche su un solo asse sarà necessario copiare la position in un Vector3 temporaneo, modificarla, e riassegnarla come nuova posizione:

Vector3 tempPos = transform.position; tempPos.x = 2f; transform.position = tempPos;

Questo farà scattare l’oggetto in posizione 2 sull’asse X, lasciando Y e Z immutate. Fortunatamente, esiste Translate un’utile funzione per traslare oggetti, utilizzabile in due modi:

transform.Translate(2f, 0f, 0f); transform.Translate(new Vector3(2f, 0f, 0f));

che ci permette anche di spostare l’oggetto nello spazio di coordinate del suo parent, e non in quello globale:

transform.Translate(new Vector3(2f, 0f, 0f), Space.Self); //notare Space.Self

Una nota importante: la funzione Translate (così come impostare la position manualmente) non muove l’oggetto spostandolo fisicamente nello spazio istante per istante, ma lo “teletrasporta” nella nuova posizione. Chiaramente se l’oggetto si sposta di incrementi abbastanza piccoli, al giocatore apparirà come se si stesse muovendo. Questo va bene nel 99% dei casi, l’unica cosa da tenere a mente è che poiché l’oggetto non tiene conto di collisioni fisiche, compenetrerà gli altri oggetti fino a passare oltre.

Per ottenere un movimento che tenga conto di muri, ostacoli e collisioni, sarà necessario scomodare il componente Rigidbody in unione con un Collider, ma di questo parleremo in un’altra lezione dedicata alla fisica.

Gerarchie di oggetti

Come abbiamo visto parlando delle gerarchie di oggetti nell’editor, in Unity è possibile inserire un oggetto dentro l’altro, allo scopo di far muover i GameObject “figli” in relazione al “padre”.

In programmazione, l’oggetto padre viene salvato nella proprietà parent del Transform. Di contro, un oggetto che ne contenga altri possiedera una proprietà childCount(numero degli oggetti figli) ed alcuni metodi per operare su di essi, come Find oGetChild. Gli oggetti sono in pratica connessi attraverso i loro componenti Transform.

Ad esempio, se un oggetto cube1 contiene un riferimento a cube2 nel parent del suo Transfrom, diciamo che cube1 è figlio di cube2. Per creare questa connessione, bisogna assegnare il Transform del padre come parent del Transform del figlio:

Debug.Log(cube2.transform.childCount); //stampa 0, perché cube2 non ha figli cube1.transform.parent = cube2.transform; Debug.Log(cube2.transform.childCount); //stampa 1, perché cube1 è stato appena

connesso a cube2

Quando un oggetto è contenuto in un altro, la sua position rimane comunque il valore della sua posizione assoluta nella scena. Per sapere la posizione relativa rispetto al parent, basterà consultare la proprietà transform.localPosition.

Per rimuovere un figlio dal parent, è necessario impostare parent a null:

cube1.parent = null;

Page 54: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

53

In questo modo, l’oggetto figlio sarà di nuovo sulla root della scena.

17.Transform: rotazione,

orientamento e scalatura Continuamo a lavorare con il componente Transform, come nella lezione precedente, questa volta analizzando rotazioni e scalature.

Ruotare gli oggetti

La rotazione viene conservata in Unity in diversi modi: ci sono le proprietà eulerAnglese localEulerAngles, che sono Vector3 e rappresentano l’angolo di rotazione nei tre diversi assi X, Y e Z in gradi (quindi da 0 a 360).

Ruotare un oggetto impostando i suoi angoli di Eulero però presenta un problema che si chiama gymbal lock (ecco anche un video esplicativo), molto conosciuto agli animatori 3D. In breve, gli angoli di rotazione che vediamo nell’Inspector hanno un ordine di applicazione, quindi sommare rotazioni su diversi assi non produce i risultati sperati perché la rotazione su un asse dipende anche dalla rotazione degli altri 2.

Per risolvere questo problema, in Unity vengono usati i una struttura matematica che incorpora numeri complessi, e che può a volte essere confusa con un Vector4 perché ha quattro componenti (x, y, z e w).

I quaternioni sono conservati nella proprietà transform.rotation, però sui quaternioni non si dovrebbe mai andare ad agire in maniera diretta (cioè modificando le componenti a mano) ma sempre con le funzioni che fornisce Unity, come Rotate,RotateAround, ecc.

È possibile invece modificare a mano gli angoli di Eulero (questo sì che si può fare), ad esempio per resettare una rotazione:

transform.localEulerAngles = new Vector3(0f, 0f, 0f);

Questa operazione modifica anche i quaternioni della proprietà rotation.

Orientamenti

Altra utile possibilità è di orientare un oggetto in modo che guardi verso un altro. In Unity, il componente Transform contiene 3 vettori che indicano l’orientamento attuale nelle tre direzioni principali: up, forward e right. Questi vettori (versori) non rappresentano posizioni né spostamenti, ma orientamenti, e come tali hanno lunghezza 1. Possono essere usati per diversi scopi.

Ad esempio, si può spostare un oggetto in avanti (il suo “avanti”) di 3 unità. Per farlo precedentemente avevamo usato Translate nello spazio locale, ma potremmo farlo anche usando l’orientamento:

transform.Translate(new Vector3(2f, 0f, 0f), Space.Self); transform.Translate(transform.forward * 2f, Space.World);

Queste due righe si equivalgono: nella prima, diciamo all’oggetto di spostarsi di due unità in avanti rispetto al suo sistema di coordinate (Space.Self).

Page 55: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

54

Nella seconda, utilizziamo transform.forward per avere un vettore lungo due unità, che punti nella direzione “avanti” dell’oggetto. In questo modo lo spazio di coordinate può essere Space.World.

Usando gli orientamenti, possiamo copiare la rotazione da un oggetto all’altro. Inoltre, possiamo invertire gli orientamenti, in modo da ottenere le direzioni giù, dietro e sinistra (che non sono disponibili come proprietà):

cube1.transform.forward = -cube2.transform.forward;

Così facendo, cube1 copierà esattamente la rotazione di cube2, ma sarà orientato al contrario, come se cube2 si fosse girato di spalle. Allo stesso modo, usare -transform.uprestituisce una direzione “verso il basso” dell’oggetto, e -transform.right verso sinistra.

Da notare che gli orientamenti che abbiamo visto sono relativi agli oggetti, ovvero locali. Anche se sono facili da immaginare, troviamo gli orientamenti globali in:

Vector3.up: (0, 1, 0);

Vector3.right: (1, 0, 0);

Vector3.forward: (0, 0, 1).

Per fare un modo che un oggetto guardi verso un altro senza calcolare la direzione, possiamo usare una funzione LookAt del Transform. Questa accetta come parametri la posizione verso cui guardare (che sarà in pratica il nuovo forward dell’oggetto da orientare), ed un vettore che indichi poi come orientare l’oggetto verso l’alto (che sarà il suo nuovo up):

transform.LookAt(Vector3.zero, -Vector3.up);

In questo esempio, l’oggetto sarà orientato per guardare verso l’origine degli assi (Vector3.zero), ma sarà anche girato a testa in giù, perché come “su” gli abbiamo dato la direzione globale “verso in basso” (-Vector3.up).

Scalature

Il fattore di scala degli oggetti si può modificare in maniera molto simile alla posizione: i valori di scala sono custoditi in un Vector3, nelle sue 3 componenti, ed esiste in versione locale e globale. Lavorare con quella globale (transform.lossyScale) non ha senso però, quindi ci ritroveremo in Unity a lavorare sempre sulla transform.localScale. Ad esempio:

transform.localScale = new Vector3(2f, 2f, 2f);

Così facendo, l’oggetto diventa il doppio della sua normale scalatura. Da notare che la scala non equivale alle dimensioni, ovvero se due oggetti hanno scala (2, 2, 2) non è detto che siano grandi lo stesso (questo dipende anche dalle dimensioni in cui sono stati modellati nel software di 3D).

La scala globale, contenuta in transform.lossyScale, è di sola lettura. È quasi inutile operare con essa, perché ovviamente dipende dalla combinazione della scala locale con la scala del parent (e di eventuali altri parent) uniti alla loro rotazione (perché a volte gli oggetti sono scalati in maniera non uniforme.

È utile anche dire che le scalature non uniformi sono in generale molto sconsigliate, perché in caso di oggetti innestati possono portare a deformazione degli oggetti figli, proprio a causa del fatto che tutti i transform devono essere combinati fra loro per ottenere la posizione finale dei vertici.

Page 56: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

55

18.Raycasting: non solo traiettorie degli spari 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.

Page 57: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

56

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 chiaveout (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 etransform, 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 Vector3nello 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).

Page 58: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

57

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

Page 59: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

58

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.

Page 60: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

59

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.

19.Gestione dell’input, tastiera e joypad In questa lezione vediamo come gestire l’input da bottoni o tasti, quindi gestiremo periferiche come tastiere o joypad come quello dell’Xbox 360.

La classe Input

La classe Input fornisce una serie di funzioni statiche necessarie per gestire l’input. Unity registra l’input di continuo e ad ogni ciclo del gioco possiamo effettuare un polling, ovvero chiedere “È stato premuto il tasto X?” e agire di conseguenza.

Perché questo controllo sia effettuato ad ogni ciclo senza il rischio di mancare nessun input, è necessario inserirlo all’interno dell’Update.

Input da tastiera: la funzione GetKey

La funzione GetKey restituisce un valore booleano e serve a rilevare se è stato premuto un tasto oppure no. Per decidere quale tasto ci serve, Unity fornisce una classe KeyCode che contiene tutti i codici dei tasti, molto utile per superare la classica difficoltà di rilevare la pressione di tasti funzione (come Ctrl, Alt, F1, etc.) su sistemi operativi diversi.

Inoltre, Unity fornisce tre funzioni per rilevare la pressione di un tasto:

GetKeyDown, rileva la prima pressione;

GetKey, rileva quando il tasto viene mantenuto premuto;

GetKeyUp, rileva il rilascio del tasto.

Come abbiamo detto, il tutto va inserito all’interno della funzione Update. Facciamo un esempio con le tre funzioni:

void Update () { if(Input.GetKey(KeyCode.Space)) { Debug.Log("Space");

Page 61: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

60

} if(Input.GetKeyDown(KeyCode.Space)) { Debug.Log("Premuto Space"); } if(Input.GetKeyUp(KeyCode.Space)) { Debug.Log("Rilasciato Space"); } }

Usare l’Input Manager e la funzione GetAxis

Immaginiamo un gioco platform: gestire il movimento del personaggio richiederebbe di rilevare l’input su almeno un asse orizzontale, per muoverlo a sinistra e a destra. Per fare ciò, dovremmo gestire due tasti (freccia destra e freccia sinistra). Ma se piuttosto che movimenti bruschi stile arcade, volessimo avere anche gli effetti di accelerazione e frenata nel movimento? Per fare questo ed altro, Unity mette a disposizione l’Input Manager.

Andando sotto Edit > Project Settings > Input, sarà possibile visualizzare l’Input Manager nel pannello Inspector. Questa interfaccia permette di definire quelli che Unity chiama “assi” di Input: non andiamo a leggere la pressione dei singoli bottoni, ma lasciamo che la gestisca Unity.

In questo pannello, i singoli tasti possono essere accoppiati a due a due per creare degli assi di movimento, di cui noi poi possiamo leggere il valore. In breve, tornando all’esempio del platform di prima, non chiediamo a Unity quanto è stato premuto il tasto destro o quello sinistro ma “che valore ha l’asse di movimento orizzontale”, un valore che può variare fra -1 ed 1.

Gli “assi” di Unity non sono utilitzzabili solo per gli assi verticale ed orizzontale, ma possono essere usati per influenzare qualunque valore debba crescere o decrescere in maniera non digitale, come ad esempio l’accelerazione di un automobile, una forza applicata ad un corpo, o l’intensità di un suono.

Per cominciare, definiamo quanti assi servono nel gioco immettendo un numero in alto nel pannello, sotto Axes:

Page 62: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

61

Appena confermiamo il valore, vedremo scomparire gli altri assi, e rimarrà solo “Horizontal”. In quest’interfaccia, Positive button e Negative button definiscono i due tasti che compongono l’asse. Nel nostro esempio, le frecce da tastiera sono l’input principale, ma l’utente può anche scegliere di premere i tasti A o D (“Alt” sta per “alternative”).

Anche se sotto Type scegliamo “Key or Mouse Button”, i valori di Gravity, Dead eSensitivity ci permettono di fare in modo che l’input assomigli ad un vero joystick, quindi di avere un comportamento analogico e non digitale, con valori dell’asse intermedi fra 0 ed 1.

In pratica, premendo destra il valore dell’asse cresce da 0 ad 1, secondo la velocità definita sotto Sensitivity (maggiore è Sensitivity, più rapidamente arriverà ad 1). Una volta arrivato ad 1 si ferma, e se lasciamo il tasto tenderà a tornare a 0 con la velocità definita sotto Gravity.

Quando premiamo sinistra, il valore inizia a decrescere per poi andare verso -1. Se viene abilitato Snap, quello che succede è che se l’asse vale 1 (perché stiamo premendo destra) e premiamo rapidamente sinistra, l’asse scatterà a 0 prima di andare verso -1. Se Snap non è attivo l’asse decrescerà ‘dolcemente’ (ma dipende dalla Sensitivity) verso 0 e poi verso -1.

Passando alla programmazione, utilizziamo la funzione GetAxis per leggere il valore dell’asse, che sarà un float fra -1 ed 1. Ad esempio:

void Update() { Debug.Log(Input.GetAxis("Horizontal")); }

Provate a premere i tasti e notate come cambiano i valori nella console. Provate anche a variare il valore di Sensitivity, portandolo a numeri piccoli come 0.1, e notate come cambia il comportamento dell’asse.

Page 63: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

62

È importante che la stringa con il nome dell’asse corrisponda a quello impostato nell’Input Manager, altrimenti Unity dà errore al momento di leggerne il valore.

Nota: il valore di Axis non viene tenuto in considerazione nel caso Type sia “Key or Mouse Button“, ma solo se è “Joystick Axis” (vedi sotto).

Input da joypad

Per rilevare l’input da joypad, è fondamentale utilizzare l’Input Manager. Lo standard dei joypad è il controller dell’Xbox 360, il cui supporto è nativo su Windows, mentre su Mac si può ottenere con il driver TattieBogle. Questo controller contiene molti assi e bottoni ed ognuno ha un nome specifico che va utilizzato in maniera precisa, ed in più variano a seconda della piattaforma. Per conoscere tutti i nomi, si possono consultarequeste immagini.

Ad esempio, se volessimo rilevare l’asse verticale del secondo stick su Mac (“4th axis”, fare riferimento all’immagine di cui sopra), il setup nel pannello Input sarebbe così:

Da notare che, visto che si tratta di una levetta di movimento, non è necessario specificare i nomi dei bottoni, quindi i campi Button sono tutti vuoti. Si noti anche cheName è arbitrario, mentre è importante che Axis sia esattamente il quarto. Joy Numdefinisce che stiamo leggendo il valore dal primo joypad connesso.

A questo punto tarando Gravity e Sensitivity possiamo regolare il nostro asse. In caso di un joystick vecchio o difettoso la cui levetta non va mai esattamente al centro, il valore Dead torna utile per definire un’area intorno al centro dell’asse in cui il valore di questi, anche se rilevato come 0.00001 o un numero simile, viene considerato comunque come 0.

Per leggere quest’asse, possiamo usare un semplice GetAxis:

void Update() { float secStickVertical = Input.GetAxis("SecondaryVertical"); if(secStickVertical != 0f) { //Muovi il personaggio //... } }

Page 64: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

63

20.L’input da mouse, touch e

sensori Proseguendo con il discorso sull’input, parliamo ora degli input provenienti da mouse, oppure dall’accelerometro o dal touch screen di un device.

Input del mouse

Così come abbiamo fatto per l’input da tastiera, anche per il mouse è disponibile una funzione che rileva la pressione dei vari tasti, a cui è necessario passare solo l’indice del tasto premuto (0 per il sinistro, 1 per il destro, e così via).

Ad esempio:

void Update ()

{

if(Input.GetMouseButton(0))

{

Debug.Log("Mouse");

}

if(Input.GetMouseButtonDown(0))

{

Debug.Log("Bottone premuto");

}

if(Input.GetMouseButtonUp(0))

{

Debug.Log("Bottone rilasciato");

}

}

Da notare che la funzione Input.GetMouseButton(0) rileva anche il touch su cellulare, ma solo il primo tocco che viene effettuato (ovvero, non funziona se sullo schermo è posizionato più di un dito).

Posizione del mouse

Come visto, rilevare la pressione di un tasto del mouse è abbastanza semplice. Come possiamo però sapere “dove” l’utente sta cliccando? I metodi sono diversi, vediamone i due principali.

In primis per sapere dove l’utente ha cliccato abbiamo la posizione del mouse in pixel, in un Vector2 leggibile dalla variabile Input.mousePosition. Poiché non sappiamo a priori quale sarà la risoluzione dello schermo dell’utente, non possiamo operare sui pixel (un utente che clicca al centro potrebbe star cliccando a 400px su un netbook, ma anche a 900px in un full HD!).

Page 65: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

64

Per questo motivo, è necessario convertire la posizione del mouse in un raggio che viene sparato dalla camera nella direzione che corrisponde al punto cliccato sullo schermo. Possiamo fare tutto ciò con la funzione ScreenPointToRay, a cui dobbiamo solo passare le coordinate del mouse. In seguito, come visto in una lezione precedente, mediante Physics.Raycast possiamo scoprire se il raggio tocca un Collider nella scena, il che vuol dire che il mouse ha cliccato effettivamente su qualcosa. Va da sé che qualunque oggetto cliccabile dovrà essere fornito di un Collider attivo della dimensione necessaria.

Facciamo tutto ciò in congiunzione con l’uso del tasto sinistro del mouse come tasto “di fuoco”:

void Update()

{

if(Input.GetButtonDown(0))

{

Ray mouseRay =

Camera.main.ScreenPointToRay(Input.mousePosition);

if(Physics.Raycast(mouseRay))

{

//Qualcosa è stato colpito!

}

}

}

In questo esempio, appena il tasto sinistro viene premuto viene creato un raggio (mouseRay) a partire dalla camera principale della scena (Camera.main), e se questo colpisce qualcosa possiamo impostare il risultato del click nell’if.

Se dovessimo aver bisogno di oggetti con un Collider ma non cliccabili, basterà spostarli su un layer differente e poi fare il controllo sul raggio mascherando il raycast con una maschera di livello, come visto nella lezione sul raycasting.

Input touch e multitouch

In modo molto simile a come viene rilevato l’input del mouse, possiamo leggere l’input di uno schermo touch screen. Inoltre, la lettura del touch screen viene facilitata in Unity perché indipendentemente dalla piattaforma (che sia iOS o Android) Unity fornisce un array di elementi Touch (ovvero Input.touches) che corrisponde a tutti i tocchi attivi in un certo momento, quindi possiamo trattare i tocchi allo stesso modo indipendentemente dal device.

Ognuno di questi elementi Touch possiede molte informazioni utili: la posizione a schermo, la fase del tocco (iniziato, mantenuto, terminato, molto simili a GetKeyDown,GetKey e GetKeyUp), ed il numero del dito che sta producendo il tocco.

Vediamo un esempio:

foreach(Touch t in Input.touches)

{

switch(t.phase)

{

Page 66: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

65

case TouchPhase.Began:

Debug.Log ("Nuovo tocco: " + t.fingerId + " pos: " + t.position.x + " " + t.position.y);

break;

case TouchPhase.Moved:

case TouchPhase.Stationary:

Debug.Log ("Tocco continuato: " + t.fingerId + " pos: " + t.position.x + " " + t.position.y);

break;

case TouchPhase.Ended:

case TouchPhase.Canceled:

Debug.Log ("Tocco terminato: " + t.fingerId + " pos: " + t.position.x + " " + t.position.y);

break;

}

}

In questo esempio, scorriamo l’array dei tocchi in un dato momento (ammesso che ce ne siano!) tramite un ciclo foreach e stampiamo a schermo le coordinate di tutti i tocchi presenti sul touch screen, distinguendoli (mendiante uno switch) in base alla fase in cui si trova il tocco (t.phase).

Lavorare con il multitouch

È importante notare come il device, ovviamente, non ha una percezione di quale dito sta toccando lo schermo. Se ad esempio premiamo lo schermo con l’indice, questo tocco sarà il primo dell’array Input.touches, ed avrà fingerId 0. Se premiamo anche con il medio, questo sarà il secondo tocco nell’array Input.touches, ed avrà fingerId 1.

Se ora solleviamo l’indice, il tocco che corrisponde al medio, essendo l’unico rimasto, sarà il primo dell’array (Input.touches[0]). Per riconoscere che è sempre lo stesso, torna utile la variabile fingerId, che avrà ancora valore 1.

Allo stesso modo, premendo con tre dita nell’ordine indice, medio ed anulare, avremo un array di tre elementi. Se solleviamo l’indice ed il medio, e poi successivamente ripremiamo con l’indice, avremo un array di due elementi (che nella realtà sono medio ed anulare) in cui non per forza Input.touches[0] sarà il medio e Input.touches[1] l’anulare, quindi dobbiamo basarci sul fingerId per sapere quale tocco corrisponde a quale dito.

In definitiva, lavorare con il multitouch presenta una serie di difficoltà relative alla gestione dei singoli tocchi, che richiede una considerevole quantità di codice.

Unity Remote: testare le caratteristiche mobile su desktop

Poiché il tocco viene registrato solo su mobile ma lì non abbiamo modo di usare Debug.Log, in mancanza di un computer che rilevi il tocco (come un tablet Surface), per poter testare i tocchi direttamente nell’editor l’unico modo è ricorrere a Unity Remote.

Unity Remote è una piccola app per iOS o Android, che permette di collegare un device (che sia un telefono o un tablet) ad una istanza di Unity in esecuzione sul computer tramite

Page 67: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

66

wi-fi, ed inviare al gioco input dal touch screen o dall’accelerometro. Unity a sua volta invia al device un video a bassa risoluzione del gameplay, in modo da poter effettuare l’input in maniera informata.

L’app è gratuita e scaricabile dagli store (App Store o Google Play) ed è uno strumento fondamentale per lo sviluppo di app gioco su questi device. L’alternativa sarebbe creare una build ogni volta che facciamo una piccola modifica, un’operazione lunga e troppo costosa nel processo di sviluppo.

In definitiva però, prima di pubblicare un gioco bisogna sempre testarlo sul device di riferimento, anche perché l’input di Unity Remote arriva con molto ritardo al computer, quindi sul device si potrebbero avere risultati leggermente diversi.

Rilevare i movimenti dell’accelerometro

Così come per il touch, anche per l’accelerometro Unity fornisce un’interfaccia unificata indipendentemente dal device: Input.acceleration, che consiste in un Vector3 che corrisponde all’accelerazione gravitazionale del device. Purtroppo, l’orientamento di questo vettore può variare di device in device, quindi a volte sarà necessario procedere per esperimenti e scoprire come è orientato.

Ad esempio poggiando il device su un tavolo, se la componente z di questo vettore assume un valore positivo mentre le altre due componenti valgono 0, vuol dire che il vettore è orientato con Z verso lo schermo, Y verso l’alto, e X verso il lato destro del telefono.

L’orientamento degli assi va però testato device per device e, in caso di differenze, sarà necessario inserire nel codice un controllo sul tipo di device utilizzato, e riordinare gli assi.

Facendo delle prove ci si renderà conto che l’accelerometro restituisce valori molto sporchi, dove i numeri non sono precisi ma tendono ad oscillare anche se il device viene tenuto su una superficie stabile. Questo perché l’accelerometro per sua natura è impreciso, ed inoltre risente di tutte le forze magnetiche anche minime nell’area circostante. Per questo motivo spesso l’accelerometro non viene usato per controllare personaggi con precisione, ma solo come misuratore degli scossoni dati al telefono.

Ad esempio:

Page 68: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

67

void Update()

{

Vector3 deviceAcceleration = Input.acceleration;

if(deviceAcceleration.magnitude > 10f)

{

Debug.Log("Il device è stato scosso forte");

}

}

Questo semplice codice legge il valore della magnitudine del vettore di accelerazione, per ricavare la forza con cui è stato scosso il device. Se questa supera 10 (un valore di esempio a caso), possiamo considerare che sia stato scosso forte.

Anche per l’accelerometro vale il discorso di Unity Remote di cui sopra: poiché non sarebbe possibile testare i valori del sensore se non facendo una build, questa app permette di testare rapidamente questo metodo di controllo durante lo sviluppo.

21.Collider, gestire le collisioni In questa lezione affronteremo tutti gli aspetti delle collisioni, sia quelle che riguardano le simulazioni fisiche, sia quelle relative ai cosiddetti trigger, ovvero elementi intangibili che possono essere usati come interruttori.

Il componente Collider

Alla base delle collisioni in Unity si trova il componente Collider. In realtà, Collider è la classe base da cui ereditano tutti i vari tipi di collider, ognuno dei quali ha una forma diversa. In una situazione reale troveremo sugli oggetti di gioco dei BoxCollider,SphereCollider, e così via, o una combinazione di questi a seconda della forma dell’oggetto.

I Collider non devono per forza corrispondere come forma alla geometria reale di un oggetto. Ad esempio, se la visuale di gioco è sufficentemente distante e non è necessario simulare le collisioni per davanzali, finestre, o per le antenne, un intero palazzo potrebbe avere un solo BoxCollider, ovvero a forma di semplice parallelepipedo.

Se serve una collisione più dettagliata, Unity mette a disposizione il componenteMeshCollider, che vedremo più avanti.

Eventi e ottimizzazione

Una volta applicato un componente Collider ad un GameObject, esso inizia a rilevare le collisioni con altri Collider nella scena e invierà a tutti gli script tre eventi:

Evento Descrizione

Page 69: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

68

OnCollisionEnter avviene nel frame in cui la collisione è iniziata

OnCollisionExit viene attivato nel frame in cui finisce la collisione

OnCollisionStay è attivo in tutti i frame in cui la collisione permane

Sarà nostro compito gestire questi eventi e programmare eventuali azioni di risposta.

Tuttavia, indipendentemente da questo, Unity eseguirà comunque i check della fisica e delle

collisioni. Perciò se un oggetto non dovrà collidere con altri, è consigliato rimuovere eventuali

Collider perché di fatto aggiungono calcoli inutili durante il gioco.

OnCollisionEnter, rilevare le collisioni

Per agire conseguentemente ad una collisione, bisogna aggiungere uno script allo stesso oggetto che possiede il collider (creando uno script e trascinandolo sull’inspector dell’oggetto), poi occorre inserire nello script uno degli handler citati prima, ad esempioOnCollisionEnter.

Tutti gli handler per questi eventi devono prendere come parametro un oggetto di tipoCollision, che ci permette di operare sulla collisione leggendone parametri come forza, direzione, e punti di contatto. Ad esempio:

private void OnCollisionEnter(Collision coll)

{

if(coll.relativeVelocity.magnitude >= 10f)

{

Destroy(gameObject);

Destroy(coll.gameObject);

}

}

In questo caso, nell’esempio utilizziamo la proprietà magnitude di relativeVelocity per rilevare urti più forti di una certa quantità decisa da noi. Se se ne verificano, distruggiamo sia l’oggetto che possiede questo script (gameObject) sia quello che ha urtato con lui (coll.gameObject).

OnCollisionStay, gestire ciò che accade durante la collisione

Esaminiamo un altro esempio: supponiamo di avere un prefab con un effetto di scintille, e di volerlo creare in corrispondenza dei punti di contatto fra due oggetti, e vogliamo farlo per tutta la durata della collisione (magari l’oggetto struscia sul pavimento…). Usiamo OnCollisionStay, ed ogni 25 frame istanziamo un nuovo prefab in corrispondenza dei punti di contatto (ovvero coll.contacts):

Page 70: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

69

private void OnCollisionStay(Collision coll)

{

if(Time.frameCount % 25 == 0)

{

foreach(ContactPoint c in coll.contacts)

{

Instantiate(sparksPrefab, c.point, Quaternion.identity);

}

}

}

Rigidbody

È importante notare che oggetti che vengono mossi mediante il loro componenteTransform (ovvero quindi modificandone la transform.position, oppure tramitetransform.Translate, o con transform.Rotate) non generano i messaggi riguardanti la fisica che abbiamo appena visto.

In altre parole un oggetto mosso così compenetrerà altri Collider come se non ci fosse stata alcuna collisione!

Per ricevere i messaggi, è necessario aggiungere un componente Rigidbody o muovere l’oggetto in altro modo con rigidbody.ApplyForce o rigidbody.ApplyTorque(come vedremo più avanti nella lezione sui Rigidbody).

MeshCollider

Invece di utilizzare una semplice forma geometrica come un cubo, una sfera o una capsula, il MeshCollider calcola le collisioni sulla base di un’effettiva geometria scelta.

Bisogna tener presente che, proprio per questa maggior precisione, il MeshCollider è il più pesante dei Collider da calcolare, percià è meglio evitarlo quando non strettamente necessario. Inoltre, un MeshCollider può collidere con un collider semplice ma non può collidere con un altro MeshCollider, a meno che uno dei due non sia impostato come “Convex”.

Quando un collider è Convex, la mesh che lo compone viene ricalcolata e semplificata in modo da essere convessa. Questo semplifica anche i calcoli delle collisioni e permette al collider di toccare un altro MeshCollider, ma ha un limite di 255 triangoli.

Ecco un esempio di come un collider per una spada risulta nella sua versione normale, e convessa:

Page 71: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

70

Mentre per Box e Sphere Collider l’Inspector presenta i valori delle dimensioni e/o del centro, nel caso di un MeshCollider questo non è presente e la dimensione dipende solo dalla mesh scelta (anche questo da Inspector).

Questo permette risvolti interessanti: un oggetto può avere graficamente un certo aspetto ma un collider di forma diversa, oppure avere molti poligoni per la mesh da renderizzare, ma avere un collider con un numero di triangoli molto ridotto (per semplificare i calcoli della fisica).

Matrici di collisione basate sui layer

È possibile ottimizzare la fisica di un gioco abilitando le collisioni solo per gli oggetti appartenenti ad alcuni layer. Possiamo anche usare questa funzione per creare degli effetti particolari.

Facendo un esempio, in uno shooter potremmo avere delle reti di fil di ferro, e volere che i proiettili non le colpiscano in modo da poter sparare attraverso di esse. Chiaramente i personaggi dovranno collidere, altrimenti le attraverserebbero come se non ci fossero. Per costriuire un sistema tale, andiamo su Edit > Project Settings > Tags and Layers e creiamo i layer che ci servono:

Page 72: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

71

Se ora assegnamo ogni oggetto al giusto layer, e poi andiamo in Edit > Project Settings > Physics, sotto Collision Matrix troveremo una matrice che permette di decidere per ogni layer, con quali layer Unity deve effettivamente rilevare le collisioni, e con quali ignorarle.

Il funzionamento è semplice: se nella casella che si trova nell’incrocio fra una riga ed una colonna c’è una spunta, gli oggetti appartenenti al layer riga o al layer colonna collideranno fra loro durante il gioco.

Organizziamo la matrice così:

In questo modo, i personaggi collideranno con tutto (pavimento, reti, proiettili), i proiettili passeranno attraverso le reti, e inoltre non urteranno fra loro (sarebbe quantomeno irreale e difficile da gestire), mentre non verranno neanche tenute in considerazione collisioni fra pavimento e pavimento, e reti con reti (sono oggetti statici quindi non devono toccarsi).

Così facendo abbiamo ottenuto l’effetto voluto ed alleggerito i calcoli che Unity deve fare per la fisica ogni frame, che sono notoriamente dispendiosi, soprattutto su mobile.

I trigger

I trigger sono una versione speciale di Collider, deputati alla creazione di oggetti non tangibili, di cui però si vogliono comunque rilevare le collisioni. È il caso ad esempio di un gioco d’avventura, in cui di solito si usano dei cubi invisibili per creare delle “aree interruttore” (trigger, appunto) in cui appena il personaggio vi entra, succede qualcosa (si chiude la porta dietro di lui, inizia una sfida, ecc.).

Per creare trigger, è necessario spuntare la casella Is Trigger nell’Inspector di un qualunque collider. Così facendo l’oggetto potrà compenetrare altri collider e non li spingerà via.

In più, i trigger non emettono i tre eventi OnCollisionEnter, OnCollisionExit,OnCollisionStay, ma una versione speciale chiamata OnTriggerEnter, OnTriggerExit, OnTriggerStay. Questi messaggi funzionano in modo molto simile agli altri, ma posseggono un parametro che non è di tipo Collision ma Collider (ovvero il collider che ha toccato l’oggetto). Per questo motivo contengono meno informazioni, ma sono anche più leggere da calcolare delle controparti OnCollision.

Page 73: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

72

Ad esempio:

private void OnTriggerEnter(Collider otherCollider)

{

if(otherCollider.gameObject.tag == "Player")

{

GameManager.Instance.CloseDoor();

}

}

In questo esempio, usiamo OnTriggerEnter per rilevare quando il giocatore entra in una stanza. Poiché il trigger potrebbe scattare anche nel caso in cui un nemico o un proiettile passi la porta, nella funzione leggiamo la proprietàotherCollider.gameObject.tag dell’altro oggetto per chiamare CloseDoor solo se è il giocatore ad entrare.

Così come per i collider normali, i trigger chiamano queste funzioni solo se almeno uno dei due oggetti coinvolti nello scontro possiede un Rigidbody ‘non kinematic’ (che vedremo nella prossima lezione sui Rigidbody).

22.Rigidbody: simulazioni fisiche con unity 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

Page 74: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

73

e le 4 unità di lunghezza, una sedia avrà una base di 1×1, 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 classeRigidbody 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 tipoForceMode 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.

Page 75: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

74

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 FixedUpdatequando 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 (quindileftRightInput 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

Page 76: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

75

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.

23.Physic material e vincoli per oggetti “fisici” In questa lezione continuiamo ad esaminare le proprietà dei Rigidbody, in particolare esaminiamo i Physic Material, che permettono di attribuire ad un oggetto effetti realistici di attrito e di rimbalzo.

Oggetti veloci

Quando un corpo si muove molto veloce, è possibile che trapassi altri corpi perché la simulazione fisica non è abbastanza ‘granulare’ da calcolare il momento dell’impatto. In un esempio pratico di un colpo di pistola, in un frame il proiettile si trova davanti al muro, e nel frame successivo l’ha già passato: in quel caso, per il motore di fisica non c’è stato impatto (ed è vero!) quindi il proiettile appare al giocatore come se avesse bucato il muro.

Per evitare questo effetto, i Rigidbody hanno un’opzione chiamata Collision Detectionche di default è Discrete. Questa impostazione va bene nel 90% dei casi, ma se si hanno problemi con corpi che trapassano muri o cose simili, si può scegliere per un corpo un valore Continuous dal menu a tendina Collision Detection.

Page 77: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

76

Nello specifico, il valore scelto deve essere Continuous Dynamic se il corpo con cui questo deve collidere si muove (ovvero possiede anche lui un Rigidbody che non èisKinematic).

Nota: La Collision Detection di tipo ‘Continuous’ o Continuous Dynamic è molto pesante a livello computazionale, e va utilizzata solo se davvero necessario.

Limitare i movimenti di un Rigidbody

Fra le proprietà dei Rigidbody abbiamo anche la possibilità di limitare il movimento (i cosiddetti Constraints). Nell’Inspector sono presenti 6 caselline che permettono di bloccare movimento e rotazione del rigidbody nei tre assi distinti.

In questo modo possiamo creare effetti particolari molto utili: ad esempio, in un gioco in cui non c’è spostamento verticale o salto, limitare l’asse Y può fare in modo che anche se il personaggio viene colpito con forza non voli via dallo scenario in alcun caso. In giochi 2D, o 3D ma con gameplay in 2D, il personaggio inquadrato di lato può avere l’asse Z bloccato (anche se in questo caso sarebbe meglio usare i nuovi strumenti per la fisica 2D che Unity mette a disposizione dalla versione 4.2 circa).

Un’altra opzione da esaminare è isKinematic, spuntarla consente di creare un Rigidbody che, pur “esistendo” nel mondo fisico, non viene influenzato dai joints, dalle forze o dalle collisioni. Questo permette di creare un corpo interamente controllato dall’animazione, ad esempio. All’occorrenza, si può sempre rimettere isKinematic sufalse in programmazione, e il corpo ricomincierà ad essere influenzato dalle forze.

È importante ricordare che due Collider che urtano fra loro non ricevono gli eventiOnCollisionEnter, OnCollisionExit, OnCollisionStay se almeno uno dei due non ha un Rigidbody. In quel caso, isKinematic permette di avere un Rigidbody anche quando non serve che il corpo si sposti.

I Physic Material

I collider, di qualunque tipo siano, possiedono un’ulteriore proprietà interessante: ilPhysic Material. Questo tipo di materiale, specifico per la fisica, definisce il modo in cui il collider reagisce al contatto con un altro collider, ammesso che abbia un Rigidbody che gli permetta di muoversi nello spazio.

I Physic Material, come i normali Material, sono dei veri e propri file che si trovano nel progetto ed hanno come estensione .physicmaterial. Per crearne uno, basta andare su Assets > Create > Physics Material, oppure cliccare col tasto destro nel pannello Project e selezionare la stessa opzione.

Quando un Physics Material è selezionato, l’inspector si presenta così:

Come si può notare, questo asset permette di gestire una serie di parametri che ne determinano le proprietà meccaniche come:

Page 78: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

77

Attrito statico (Static Friction), ovvero la resistenza dell’oggetto a mettersi in moto;

Attrito dinamico (Dynamic Friction), ovvero la resistenza che ha quando ormai è in

movimento, e che lo fa fermare di nuovo.

Elasticità (Bounciness), definisce il fattore di rimbalzo: un corpo che abbia 0 assorbe

completamente i colpi, mentre uno che abbia bounciness 1 li restituisce con potenza uguale.

Ad esempio, una palla con bounciness 1, se lasciata cadere non smetterà mai di rimbalzare

perché la forza con cui rimbalza non decresce mai.

I valori di combine indicano, in caso di impatto con un altro Physic Material, come vengono calcolate la frizione e il rimbalzo: il risultato può essere una media dei due (Average), il più piccolo dei due, il più alto, o il prodotto dei due valori.

La proprietà FrictionDirection2 è un’opzione interessante ma difficilmente utile: se diverso da 0, aggiunge una secondo attrito diverso dal primo che si attiva solo quando il corpo si muove nella direzione indicata dal vettore. Permette quindi di creare effetti particolari come un corpo che scivola quando va in avanti, ma che applica una forte resistenza quando si muove sui lati.

Per usare un Physic Material, l’unica cosa necessaria è di crearlo nel Project e assegnarlo ad un Collider. È possibile trovare Physic Material già pronti in un package di Unity (ovvero una collezione di asset predefiniti), che si trova in Assets > Import Package > Physics Materials.

24.I joint I Joint sono lo strumento tramite il quale Unity permette di utilizzare oggetti e modelli tridimensionali come se fossero marionette. Il motore fisico di Unity permette infatti di creare diversi tipi di giunture (Joint appunto) per ottenere effetti particolari, di solito per limitare il movimento di un corpo nello spazio.

I Joint ed il motore fisico

Esiste un componente per ogni tipo di Joint, e per assegnarli si può seguire la solita procedura da menu Component > Physics > Hinge Joint (ad esempio), o dal tasto Add Component dell’Inspector.

Per assegnare un Joint, è necessario che il corpo abbia anche un Rigidbody, ma non deve per forza avere un Collider: il corpo potrebbe ubbidire alle leggi della fisica, ma essere intangibile. Niente vieta di aggiungere al corpo spinte e forze varie in programmazione (come visto nella lezione sui Rigidbody) e venir limitato dai Joint.

I Joint si possono vedere in azione solo quando la scena è in Play. Se i corpi influenzati da Joint vengono mossi quando la scena è in Pausa, è possibile che al Play li si vedrà scattare in posizione per recuperare la posizione selezionata nelle proprietà dei Joint stessi.

Prima di andare avanti fissiamo bene in mente che quasi tutte le proprietà dei Joint che esaminiamo sono accessibili, oltre che dall’Inspector, anche in programmazione, semplicemente utilizzando la funzione GetComponent per accedere al Joint, e operando su di esso come già visto con i componenti Transform, Collider, Rigidbody, etc.

FixedJoint

Il FixedJoint è una giunzione di tipo fisso che funziona in maniera molto simile all’imparentamento fra GameObject, creando un collegamento fisso fra il GameObject e lo

Page 79: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

78

spazio, o fra due GameObject. Quando viene applicato un FixedJoint ad un corpo, questi cerca di mantenere la propria posizione nello spazio.

Ecco come appare nell’Inspector:

Il FixedJoint, come molti altri Joint, ha una proprietà chiamata Connected Body, che può essere nulla o contenere un riferimento ad un Rigidbody presente su un altro oggetto. Se non è nulla, il corpo con il Fixed Joint cercherà di seguire i movimenti del GameObject connesso. In alcuni casi può succedere che per urti e compenetrazioni il corpo non riesca a mantenere la posizione relativa: in questo caso si comporterà come se fosse legato da una molla, cercando di tornare alla posizione in cui si trovava.

NOTA: Quando un corpo è connesso ad un altro con Connected Body, smette di collidere con questo.

Se il corpo limitato dal Joint prende un urto, i valori Break Force e Break Torqueindicano i limiti massimi al di sopra dei quali il Joint si rompe, e viene rimosso. Di default questi due campi hanno come valore Infinity, che sta a significare che il Joint è indistruttibile. I limiti di rottura non sono specifici del FixedJoint, ma ricorrono un po’ in tutti i Joint.

I cardini – HingeJoint

Gli HingeJoint, come dice il nome, sono giunture utilizzate per creare connessioni simili ai cardini di una porta, dove un corpo viene forzato a ruotare su un asse (che non per forza deve passare per il suo centro).

Poiché è costretto a ruotare su quest’asse, il corpo non si può muovere liberamente nello spazio. Fa eccezione il caso in cui il corpo sia legato ad un altro (mediante imparentamento o selezionando un corpo ConnectedBody), permettendo il movimento insieme al suo parent.

Assegnamo un HingeJoint ad un corpo. L’Inspector si presenterà in maniera simile:

I campi fondamentali sono:

Page 80: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

79

Anchor: è un vettore che indica la posizione nello spazio dell’asse su cui l’oggetto ruota, in

coordinate locali. In quest’esempio, essendo l’oggetto un cubo da un unità, 0.5 sull’asse Y

vuol dire che l’oggetto ruota come se avesse un cardine applicato al suo lato superiore.

I valori di Axis definiscono l’orientamento dell’asse di rotazione, e sono un vettore che indica

una direzione: quel (0, 0, 1) in figura indica una rotazione lungo l’asse Z. Anche qui

l’orientamento è locale, quindi anche se l’oggetto viene ruotato nello spazio, una volta in Play

inizierà ad oscillare lungo il suo asse Z, e non quello del mondo.

Altro esempio: un valore di (1, 0, 1) indica un asse a 45° fra Z e X. Per inciso, un valore di (0.5, 0, 0.5) avrebbe lo stesso effetto.

Nella scena vedremo una situazione simile:

In questo setup, al cubo blu è stato assegnato un HingeJoint, che lo costringe a ruotare su un asse rappresentato nell’immagine da una piccola freccia arancione che punta verso destra, parallela al suo asse Z. La freccia arancione è piccola e potrebbe essere difficile vederla, ma è un’utile indicazione per capire su quale asse è costretto il corpo e dove si trova il centro della rotazione.

NOTA: Meglio non modificare i valori di Connected Anchor, e lasciare la spunta su Auto Configure Connected Anchor. In questo modo Unity sceglierà in autonomia dove posizionare l’asse di rotazione per l’oggetto connesso, e non ci saranno effetti indesiderati. Configurare questo valore a mano è il più delle volte inutile.

Assegniamo il parallelepipedo giallo nella proprietà ConnectedBody, e mettiamo il suo Rigidbody come isKinematic, in modo che non cada per la gravità e non si sposti. Il cubo blu è ora ancorato lungo un cardine orizzontale, e “pende” letteralmente da quello giallo.

NOTA: legare un Joint ad un altro oggetto che abbia il Rigidbody su isKinematic e che non si muove, è un po’ come non legarlo a nessun oggetto, con l’unica differenza (come detto sopra) che se gli oggetti sono legati fra loro, non collidono.

Se mandiamo la scena in Play e muoviamo l’oggetto arancione in orizzontale, il cubo blu penzolerà ed è possibile che ad angoli estremi compenetri il suo parent, così:

Page 81: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

80

Per evitare questo effetto, possiamo limitare la libertà di movimento sull’asse fino ad un angolo limite. Spuntiamo Use Limits, ed assegnamo questi valori: Min -80, Max 80. In questo modo, il corpo girerà intorno all’asse Z solo di 80 gradi in entrambi i sensi. Se vogliamo, possiamo assegnare i valori di Min Bounce e Max Bounce, per far rimbalzare il corpo in caso arrivi a questo angolo massimo.

Altra proprietà utile degli HingeJoint, è Use Motor. Attivando questa spunta, l’oggetto inizierà a girare intorno all’asse di moto proprio, come se avesse un motore. Grazie alle proprietà Target Velocity e Force, possiamo definire la forza del motore e la velocità rotazionale da raggiungere. Free Spin indica se il motore agisce solo da acceleratore del corpo o lo frena anche.

C’è anche la possibilità di trattare il cardine come se fosse ‘a molla’. Spuntando Use Spring, l’oggetto tenderà a tornare ad un certo angolo (ammesso che ce la faccia, visto che un oggetto o la gravità potrebbero bloccarlo). L’angolo si può scegliere con Target Position, mentre Spring e Damper definiscono rispettivamente la forza della molla, e la tendenza della molla ad andare oltre il valore limite.

Chiaramente, se il valore di Target Position è fuori dai limiti imposti da Use Limits, si otterranno effetti indesiderati. Il consiglio in questi casi è di testare i vari limiti (Limits, Spring ma anche Motor) in maniera indipendente, prima di attivarli insieme, per capire meglio se stanno funzionando nella maniera desiderata o no.

In ultimo, precisiamo che se è attivo Use Motor, l’oggetto non si comporterà come una molla anche se Use Spring è spuntato: in pratica, motore e molla sono incompatibili.Use Limits invece funziona in entrambi i casi.

Le molle – Spring Joint

Molto simili agli HingeJoint, le molle sono una giuntura che crea un collegamento molleggiato, per cui il corpo, che in generale può muoversi e ruotare su tutti gli assi, cerca comunque di tornare ad una posizione impostata dall’Anchor del HingeJoint.

Sostituendo uno SpringJoint all’HingeJoint al cubo di prima, la situazione sarà questa:

Page 82: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

81

Come si vede in figura, un piccolo cubetto giallo indica la posizione dell’Anchor da raggiungere (1), ed un altro quella attuale del cubo blu (2). La molla applicherà la sua forza (il parametro Strength) per cercare di far combaciare i due cubetti, con un’elasticità indicata dal parametro Damper.

In questo scenario, di nuovo, entrano in gioco gli altri oggetti che potrebbero urtare il corpo, e la gravità, che potrebbero fare in modo che il corpo non torni mai alla posizione di partenza se il valore di Strength non è abbastanza alto.

In più, i valori di Max Distance e Min Distance indicano l’area di azione della molla: il corpo cercherà di raggiungere l’Anchor solo se si trova ad una distanza inferiore a Min Distance, e ad una superiore a Max Distance. Ad esempio, se i valori sono rispettivamente 0 e 1, la molla verrà attivata solo se il corpo è più lontano di 1 unità dall’Anchor. Quando il corpo si è avvicinato fino ad essere a meno di un unità di distanza, la molla si disattiva (attenzione: si disattiva, non viene distrutta come nel caso di Break Force e Break Torque).

Character Joint

Il Character Joint non è altro che un Hinge Joint su due assi piuttosto che uno, e perciò si può utilizzare per simulare le giunture di un personaggio, come gomiti e ginocchia. L’Inspector si presenta così:

Page 83: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

82

Questo Joint possiede un asse di rotazione (Axis) come l’Hinge Joint, ed un secondo asse di libertà chiamato Swing Axis. Entrambi gli assi hanno dei limits (sempre attivi), chiamati rispettivamente Low/High Limit per l’Axis (che qui vanno da -20 a 70 gradi), eSwing1/Swing2 Limit per lo Swing Axis (qui a 0), ed ogni limite ha la sua Bounciness, Spring e Damper (si veda sopra).

In pratica, un ginocchio avrà un Axis il cui limite positivo sarà quasi 180° (gamba piegata) mentre quello negativo prossimo a zero (si spezzerebbe il ginocchio!), mentre il suo Swing Axis avrà valori su entrambi i lati ma molto piccoli, per permettere una piccola torsione della gamba. Un braccio invece avrà un Axis simile ma uno Swing Axis con limiti molto superiori.

Se Swing1/Swing2 Limit sono impostati entrambi a 0 come nella figura sopra, non potendosi muovere sullo Swing Axis questo Joint si comporta di fatto esattamente come l’HingeJoint.

In caso di personaggio morto per un impatto, si potrebbe pensare di agire in programmazione aumentando i limiti del Joint, per permettere valori impossibili che facciano sembrare che il personaggio ha subito una frattura di un arto.

Configurable Joint

Il Configurable Joint è, come dice il nome, una giuntura completamente configurabile in tutti i suoi aspetti. L’Inspector appare così:

Page 84: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

83

Il Configurable Joint dà massima libertà di configurazione, permettendo di settare un asse di rotazione, un motore (i valori di Drive) e dei limiti come nel caso dell’HingeJoint, ma anche un molleggiamento come per lo SpringJoint, ed ancora di impostare un limite massimo al di sopra del quale il Joint si resetta (le proprietà Projection), e infine di settare tutti i valori in World Space piuttosto che come valori locali.

Di fatto, questo Joint permette di ricreare tutti gli altri ed anche di più, ma è così complesso che si sconsiglia l’utilizzo se non si è molto esperti e/o si vuole ottenere un risultato davvero particolare. La descrizione dei singoli parametri esula dal campo di questa guida. Per i dettagli su tutte le proprietà, potete andare sulla descrizione del componente nella guida di Unity.

Page 85: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

84

24.Mesh 3D, gestire oggetti e modelli Parliamo ora di tutto quello che concerne il rendering di oggetti a schermo, partendo da una breve introduzione sulle mesh 3D e su come importarle, e passando al componente Renderer, che sta alla base di tutti gli oggetti 3D visibili in scena.

Le mesh 3D

Le mesh 3D sono l’oggetto più comune nei videogiochi che incorporano grafica 3D, e sono, nella loro versione più basica, delle griglie 3D composte da una serie di vertici, edge, e triangoli/poligoni. Nello specifico:

i vertici sono punti nello spazio 3D;

gli edge (o segmenti) sono le linee che li connettono fra loro;

i poligoni sono le superfici che si vengono a formare quando 3 o più vertici vengono uniti da

edge.

Alla base delle mesh 3D per videogiochi ci sono sempre poligoni triangolari (itriangles, o tris), quindi di fatto anche un quadrato si può pensare come formato da due triangoli che condividono un edge. Un semplice cubo è infatti una mesh composta da 6 quadrati (e corrispondenti 8 vertici, 12 edge), ma una volta importato in Unity diventeranno 12 triangoli (e quindi 18 edge). Questo vale per tutti gli engine ed è sempre da tenere a mente quando bisogna specificare la “risoluzione poligonale” di un modello per i videogame.

Questa è un esempio di una scena più elaborata, composta da diverse mesh 3D:

Page 86: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

85

In quest’immagine è possibile vedere chiaramente la struttura dei vari oggetti. Nella fascia centrale, oltre alla griglia dei vertici abbiamo visualizzato anche le texture (e le luci che le influenzano), ma di texture parleremo fra un po’. Per visualizzare la griglia delle mesh 3D come sopra, si possono utilizzare le opzioni di visualizzazione che si trovano in alto nel pannello Scene View:

Opzioni Descrizione

Textured permette di vedere la scena in maniera molto simile a come sarà in

gioco, con luci e texture applicate

Wireframe mostra solo la griglia dei poligoni (il wireframe, appunto)

Page 87: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

86

Textured

Wire

mostra un misto dei due (come sopra, nella fascia centrale)

Importare una mesh 3D

Per utilizzare mesh 3D in Unity esistono due modi: il più semplice è usare le primitive che Unity mette a disposizione, come il cubo, la sfera, il piano, il quad, ecc. Per usare mesh personalizzate, bisogna importare un file 3D creato in un programma di modellazione come 3D Studio Max, Maya, Blender, Modo, ecc. I formati importati da Unity sono molti (si possono vedere qui) ed in generale equivalenti (vale a dire, il grafico che li crea può usare quello che vuole).

L’unica distinzione importante da fare riguarda i file proprietari (come il .max di 3D Studio Max, il .mb di Maya, o il .blend di Blender) contro quelli esportati più comuni (come l’.fbx, l’.obj). I file esportati comuni sono in genere più leggeri e non contengono dati inutili, e più rapidi da importare. I file di tipo proprietario sono generalmente più pesanti da importare, creano a volte dei GameObject inutili in scena, e soprattutto richiedono una copia del software in cui sono stati creati. Nel momento in cui il file va importato, Unity aprirà il software in background per la conversione, aggiungendo tempo ogni volta che bisogna reimportare (è un’operazione abbastanza comune).

Una volta creato il file, basta piazzarlo in una cartella qualunque sotto /Assets e Unity lo importerà. Selezionando il file nel pannello Project, possiamo vedere nell’Inspector leproprietà di importazione, e modificarle (e reimportare premendo Apply):

Page 88: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

87

Nell’immagine di sopra, un modello .fbx di un personaggio è stato importato, e come tale nel pannello Project mostra, oltre che alla mesh contenuta (l’oggetto chiamato_Gooy_UNW), anche le clip di animazione che il grafico ha prodotto ed esportato nel file (quelle col simbolo di play, Default Take, GooyIdle1, etc.).

A destra, possiamo vedere le opzioni di importazione, divise in tre tab (Model, Rig ed Animations). Lo Scale Factor indica la scala alla quale viene importato il modello: spesso la scala in modellazione varia di programma in programma, quindi con questa opzione possiamo correggerla per adattarla a quella di altri modelli del gioco.

NOTA: la scala di importazione non è la scala in-game, vale a dire che un modello come questo, importato con Scale Factor 0.01, avrà comunque scala (1,1,1) una volta messo in

Page 89: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

88

scena. Se scegliessimo uno Scale Factor 1 in fase di importazione, sarebbe 100 volte più grande pur rimanendo come scala di scena (1,1,1) nel proprio Transform.

Altro parametro degno di nota è Normals & Tangents: le normali sono dei vettori che indicano la direzione “uscente” da un poligono. Nel rendering 3D, vengono usate per indicare dove ‘guarda’ una faccia, al fine di determinare diverse cose: l’angolo della luce, la smussatura fra due facce, etc.

Immagine da Wikipedia

Di solito le normali vengono create nei software di modellazione in automatico. In caso di problemi però, possiamo chiedere a Unity di ‘indovinare’ le normali della superfice, scegliendo Calculate dal menu a tendina di Normals. In generale quest’opzione è la migliore, ma così facendo perderemo qualsiasi smusso personalizzato che potremmo aver creato nel programma di modellazione 3D.

Ad esempio, in quest’immagine ci sono due modelli identici di una semplice tastiera, con opzioni di importazione differente:

Page 90: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

89

Quella a sinistra conserva le normali create in Modo, che essendo state create appositamente senza smusso gli danno un aspetto “cubettoso”, molto lowpoly.

Quella a destra è stata importata con le normals su Calculate, e lo slider Smoothing Angle su 180. Questo vuol dire che le normali delle facce che creano un angolo fino a 180° vengono ricalcolate per far sembrare le facce smussate. In questo caso, la tastiera assume un look morbido, smussato, quasi gommoso, perché le normali danno l’impressione che gli angoli fra i tasti siano stati ammorbiditi (anche se la struttura 3D è identica per entrambi i modelli).

Altre opzioni di importazione degne di nota:

Mesh Compression permette a Unity di cambiare la geometria al fine di ottimizzare,

eliminando vertici rimasti soli, o poligoni inutili. Può essere utile farlo, ma solo se le geometrie

risultanti non risultano rotte o mostrano artefatti grafici, nel qual caso è meglio lasciarlo su Off.

Optimize Mesh riordina gli indici dei vertici e dei poligoni, per permettere delle performance

leggermente migliori. Anche qui, se è possibile utilizzarlo e non dà problemi – bene. Se capita

che il gioco vada in crash per motivi sconosciuti, provate a togliere la spunta a quest’opzione

per vedere se il problema nasce da questo.

I componenti Mesh Filter e Mesh Renderer

Per visualizzare un oggetto, servono una combinazione di due componenti: Mesh Filter e Mesh Renderer.

Il Mesh Filter ha una sola funzione: selezionare una mesh (fra le primitive di base, e quelle importate) che sarà poi renderizzata dal Mesh Renderer. Se si clicca sul cerchietto nel campo Mesh, verrà aperto un browser delle mesh presenti nel progetto:

Page 91: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

90

In rosso sono evidenziate le primitive di Unity, che sono interne al motore e non possono essere modificate (se non nella scalatura).

Il Mesh Filter definisce la forma di un oggetto: di fatto, un GameObject non ha una forma intrinseca, ma dipende solo dalla mesh selezionata. Così, un oggetto che rappresenta una casa può diventare un pallone da calcio se viene selezionata un’altra mesh, e mantenere tutte le altre proprietà (scala, rotazione, posizione, ma anche altri componenti, script, ecc.).

Il Mesh Renderer, come detto, si occupa di disegnare a schermo la mesh selezionata nel Mesh Filter. L’inspector è composto da tre parti (Shadow, Material e Light Probe) e si presenta così:

Dedicheremo ad ognuno di questi argomenti una lezione, la cosa importante da sapere è che il Renderer ha bisogno sempre di 1 o più Material per definire come disegnare l’oggetto.

Page 92: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

91

I Material, che sono degli asset presenti nel progetto sotto forma di file .mat, contengono tutta una serie di informazioni che nell’immagine di sopra sono visualizzate in basso sotto al componente Mesh Renderer (in questo caso il material si chiamaDefault). È importante però notare che i Material non sono componenti, anche se li si potrebbe confondere come tali perché visualizzati nell’Inspector.

Essi sono proprietà del Mesh Renderer, infatti cliccando su quel Default nel campoElement 0, e premendo Delete (o Canc su Windows), rimuoveremo il materiale dal renderer. Unity adesso non sa come disegnare quest’oggetto, e lo mostra con il tipico colore magenta del materiale mancante:

Si noti come anche il materiale Default sia sparito dall’Inspector (ma possiamo trovarlo nel pannello Project). Sarà possibile riassegnare il materiale cliccando sul cerchietto a fianco di Element 0, e selezionando di nuovo Default dal pannello di selezione materiali (simile a quello delle mesh visto sopra).

Visto che un materiale è solo una proprietà del Mesh Renderer, di fatto più renderer possono condividere un materiale, che hanno quindi tutti le stesse proprietà (come una volta o un muro, entrambi di mattoni). È anche bene che lo facciano, perché facendo così permettiamo a Unity di disegnare in una sola passata più oggetti con lo stesso materiale (quest’operazione si chiama batching, e se ne può leggere in dettaglio qui), e questo ottimizza di molto l’operazione di rendering.

Esistono altri componenti deputati al rendering di altri tipi di oggetti (ClothRenderer, TrailRenderer) che spesso non hanno bisogno del MeshFilter, ma ne parleremo più avanti.

26.Materiali e shader In questa lezione esploriamo i materiali e gli shader, ovvero ciò che definisce il “come” un oggetto 3D viene renderizzato a schermo.

Cos’è uno shader

Gli shader sono dei modelli matematici che indicano alla scheda grafica come rendere a schermo un oggetto 3D. Descrivono, essenzialmente, come (e se) l’oggetto riceve la luce, che colore ha, se proietta ombre, se riceve ombre, se ha una traslucenza, se crea rifrazioni, se è lucido, opaco, trasparente, e una miriade di altre informazioni.

Com’è ovvio che sia, esistono shader molto complessi, utilizzati su piattaforme PC, e shader semplificati che non tengono conto di molte di queste informazioni, ottimizzati per piattaforme mobile o poco potenti.

Page 93: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

92

Allo stesso modo esistono shader deputati a creare effetti non reali, come gli shader cartoon, che descrivono sugli oggetti delle ombreggiature non naturali ma a fasce più o meno nette, come nei cartoni animati. Alcuni di questi disegnano dei contorni neri intorno alle figure.

Vertex Shader e Pixel Shader

Gli shader si dividono in diverse tipologie. Fra le più usate abbiamo i Vertex Shader, comuni un tempo, che operano a livello di vertici: leggono le caratteristiche della geometria, e partendo dai vertici si ricavano quello che c’è in mezzo. Ad esempio, possono leggere le informazioni di luce dei tre vertici che sottendono un poligono triangolare, e fare la media per disegnare il centro del poligono. È chiaramente un modello impreciso, ma molto molto rapido.

Di contro i Pixel Shader sono shader che lavorano “al pixel”: semplificando molto, per ogni pixel che devono renderizzare, partono da un punto sul modello 3D, vanno al corrispondente texel sulla texture, ne leggono il colore, ne leggono il bump (se presente), incrociano il tutto con i valori della luce, e producono il pixel finale che sarà renderizzato. Quest’operazione è chiaramente più pesante rispetto a quella che fa un Vertex Shader, e lo diventa esponenzialmente di più con l’aumentare della risoluzione a schermo (più pixel, molti più calcoli).

Vediamo un esempio di differenze fra un Vertex Shader contro un Pixel Shader, in cui entrambi gestiscono la specular, ovvero il riflesso sulla superficie di una luce forte. Ecco una sfera che utilizza un Pixel Shader:

Pixel Shader

Come si può notare (anche se il modello è molto “low-poly”), il pixel shader crea dei giochi di luce più interessanti: la luce sembra venir riflessa in maniera più naturale e crea un alone tondo.

Se cambiamo il materiale affinché usi un Vertex Shader:

Vertex Shader

Page 94: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

93

Nel caso del Vertex Shader, poiché questi legge solo le informazione di luce in corrispondenza dei vertici e poi fa un’interpolazione, il riflesso è circoscritto ai poligoni su cui è più forte, ovvero quei 6 triangoli che in figura sono più chiari.

Altre tipologie di shader includono i cosiddetti Unlit (ovvero non illuminati), che non tengono conto delle luci di scena, così come esistono shader Self-Illuminated, che tengono conto della luce ma aggiungono anche una luce propria. Per una lista di tutti gli shader, si può consultare quest’elenco.

Gli shader in Unity

Ecco come si presenta la lista delle famiglie di shader inclusi in Unity, quando selezioniamo lo shader in un Material:

Page 95: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

94

In generale gli shader in Unity sono rappresentati da file .shader, ma Unity contiene tutta una serie di shader di base (quelli nell’elenco sopra appunto) che non appaiono esplicitamente nelle cartelle del progetto e che non sono modificabili.

Per importare nuovi shader, basta piazzare il file .shader nel progetto, e Unity alla prima occasione lo compilerà e lo renderà disponibile per i materiali (vedi sotto). Nellacommunity Unify sono presenti diversi shader pronti all’uso, da importare nel proprio progetto come file .shader.

I Materiali

Ogni Materiale, in Unity, ha alla base uno shader che definisce come viene disegnato ed ombreggiato. Così come lo shader definisce le formule alla base del modello fisico, il materiale definisce i singoli parametri di questo modello.

Potremmo guardare ai materiali come se fossero specifiche regolazioni di uno shader: due materiali che utilizzano lo stesso shader avranno differenze di colore, di texture, di brillantezza, ecc., ma gestiranno la luce e le ombre secondo lo stesso modello fisico.

I materiali in Unity sono dei file .mat che si trovano nel progetto, possiamo quindi crearne uno semplicemente andando nel pannello Project e scegliendo Create > Material (anche dal menu in alto, sotto Assets > Create > Material). Verrà creato un file che possiamo rinominare. Selezionandolo, l’Inspector apparirà inizialmente così:

Questo materiale usa il semplice shader Diffuse, che è un Pixel Shader che supporta un colore, ed una texture. In basso, una finestrella mostra un’anteprima del materiale su una forma di base (che è possibile cambiare tramite i tastini sulla destra, vicino al play).

Abilitare le trasparenze

Essendo Diffuse uno shader non trasparente, se selezioniamo come Main Color un colore con trasparenza (vale a dire, impostiamo ‘alpha nel pannello sotto) noteremo che l’oggetto non diventa semitrasparente e non scompare. Per renderlo semitrasparente, dobbiamo assegnare uno shader che supporti l’alpha: clicchiamo sul menu a tendina Shader, e selezioniamo Transparent > Diffuse.

Page 96: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

95

L’Inspector quasi non è cambiato, ma se modifichiamo i valori dell’alpha del Main Color l’oggetto diventerà trasparente, perché lo shader utilizzato dal materiale tiene conto di questa proprietà.

Assegnare i materiali alle mesh

Ora che abbiamo creato il materiale, possiamo assegnarlo agli oggetti in scena o ai prefab nel Project. Per farlo ci sono diversi metodi: si può trascinarli direttamente dal Project all’oggetto nella Scene View, oppure si può andare nel Mesh Renderer di un oggetto e selezionare il Material tramite l’apposito browser, a cui si accede premendo le iconcine a forma di cerchietto.

Shader complessi

Se vogliamo vedere uno shader più complesso in azione, selezioniamo come shaderSelf-Illumination > Bumped Specular. L’Inspector apparirà così:

In questo caso, questo shader supporta tutta una serie di parametri extra: autoilluminazione, riflesso della luce (specular), lucentezza (shininess), ed addirittura una texture Normal per aggiungere effetti di rilievo (Normal Map).

Unity mostra anche un avviso: poiché abbiamo scelto uno shader che supporta le normal map, ma non ne stiamo usando una, potrebbe convenire sceglierne un altro perché altrimenti il renderer farà dei calcoli extra (per le normali) che non produrranno un effetto visibile.

Vedremo le texture ed altri parametri dei materiali più in dettaglio nelle lezioni specifiche.

27.Texture Quasi sempre il colore di un oggetto 3D deriva da una texture. In questa lezione vedremo come le texture vengono importate in Unity, impostate, ed assegnate agli oggetti in scena.

Page 97: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

96

In generale, le texture sono dei file di grafica 2D che vengono sovrapposti o avvolti intorno ad un modello 3D per decorarlo. L’aspetto finale del modello dipende da tante cose (la luce, l’ambient light, ecc. ma anche lo specular) e potrebbe essere influenzato anche da un colore di fondo che possiamo decidere, e che lo shader mischia alla texture (come nel caso degli shader Diffuse) in fase di rendering.

Le texture servono a diversi scopi. Le più comuni sono dette diffuse, e rappresentano il colore dell’oggetto. A seconda dello shader, un materiale può utilizzare altre texture per ottenere effetti particolari, come l’autoilluminazione, la specularità, o i rilievi (mediante le Normal Maps di cui parleremo più avanti).

I pixel che compongono una texture si chiamano texel (texture pixel). Facciamo questa distinzione perché, quando la texture viene applicata al modello, quasi mai i texel corrispondono ai pixel a schermo. Immaginiamo una texture di 64×64 texel, su un qualunque modello 3D: a schermo il modello potrebbe occupare una manciata di pixel se lontano, o riempire tutto uno schermo full HD se molto vicino (in questo caso vedremo la texture sgranata).

Importare le texture, i formati di file

Per creare una texture, è sufficiente inserire un file grafico nella cartella degli Asset. Unity importerà il file, assegnandogli delle caratteristiche di base che potremo modificare. È importante tener conto che Unity non modifica mai l’asset originale ma ne fa una copia interna, permettendoci di importarlo più e più volte senza perdita di qualità.

Unity supporta diversi tipi di formati file per le texture: .gif, .jpg, .png, .tiff, .tga, .psde altri. Idealmente, si potrebbe pensare di usare il .jpg per produrre un file leggero, ma non è necessario: in fase di importazione Unity ricomprime il file, generando l’asset che poi finirà nel gioco. Per questo motivo, in generale è consigliabile esportare da Photoshop (o altri) in PNG 24, per conservare tutti i dettagli inclusa la trasparenza.

Alternativamente, si può lavorare in .psd ed importare direttamente quello. Unity si occuperà di compattare i livelli e creare una versione ottimizzata della texture, senza modificare il .psd originale dove quindi i livelli rimangono editabili.

Questo dà la massima flessibilità e rapidità (perché non bisogna esportare, solo modificare il file e salvare), ma genera file più grandi da gestire durante il flusso di lavoro, specialmente se si usano sistemi di versioning o bisogna passare le texture agli altri membri del team via internet.

Proprietà di importazione

Quando una texture viene aggiunta a Unity, viene importata con le impostazioni di default. Nell’Inspector vedremo:

Page 98: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

97

Proprietà Descrizione

Texture Type È la modalità di importazione. Ne esistono altre oltre a Texture, le vedremo

sotto.

Alpha from

Grayscale

Dice a Unity di ricavare un canale alpha dai valori di luminosità della texture

(bianco = opaco, nero = trasparente). In questo modo possiamo avere

texture trasparenti anche se non hanno un canale alpha. In realtà, se

lavorate con criterio non dovrebbe esserci bisogno di attivare quest’opzione!

Alpha is

Transparency

Serve a migliorare la resa dei contorni in caso di oggetti semitrasparenti. Ha a

che fare con la premoltiplicazione del colore, e meriterebbe un capitolo a sé.

Diciamo che va attivato solo in caso un’immagine con trasparenza presenti

dei bordi con artefatti grafici non voluti.

Del Filter Mode e del Wrap Mode parliamo più avanti in sezioni dedicate.

Nel pannellino in basso possiamo decidere la dimensione alla quale la texture viene importata. Max Size consente di trattare una texture come se fosse più piccola, per cui possiamo avere un file di lavoro a 2048×2048 (magari un .psd, come dicevamo prima) ma far sì che Unity lo importi ad una dimensione di 1024×1024.

Inoltre, usando le tab (le iconcine del mondo, la freccia, l’iPhone… ) possiamo personalizzare questa dimensione per ogni piattaforma. Così facendo, possiamo fare in modo che solo su Android la nostra texture si riduca ancora, diventando 512×512, per alleggerire build e rendering. Tutto questo viene fatto senza modificare l’asset iniziale.

Format invece consente di impostare l’algoritmo di compressione. Gli algoritmi sono tanti, ma si dividono in 3 classi di base: Compressed, 16 bits, e Truecolor (24 o 32 bit). Ognuno di questi ha la sua qualità, ed il suo peso in memoria, e saremo noi a dover scegliere ad occhio il miglior compromesso qualità/memoria usata.

Selezionandone uno e premendo Apply, in basso possiamo vedere il risultato visivo, e quanto spazio occuperà nella memoria video la texture una volta caricata (1 KB nell’esempio sotto):

Page 99: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

98

Anche qui possiamo fare scelte diverse per piattaforma (non sempre necessario).

Opzioni avanzate

Selezionando Advanced nel primo menu a tendina, si aprono tutta una serie di nuove opzioni:

Non Power of 2

Questa proprietà ci fa presente un nuovo problema: alle schede grafiche piacciono le texture che sono potenze di due (2, 4, 8, 16… 32… 128, ecc.), quindi se la texture non lo è Unity la converte, secondo la modalità scelta qui. Come detto per l’opzione Alpha from Grayscale, un’adeguata pianificazione in fase di disegno della texture è la soluzione migliore.

Generate Cubemap

Indica a Unity di creare una cubemap a partire da una semplice texture. Una cubemap è una texture che non è piana, ma a forma di cubo. Le cubemap di solito vengono usate per descrivere l’ambiente circostante ad un oggetto, ad esempio per mostrare cosa l’oggetto riflette, o per creare le skybox.

Page 100: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

99

Di fatto, le cubemap spesso contengono una deformazione prospettica che permette di usare le sei facce del cubo come fossero una sfera, per fare in modo che non si vedano gli spigoli del cubo. Per fare un esempio comune, una cubemap è quello che viene visualizzato in Street View all’interno di Google Maps: un’immagine sferica che avvolge l’utente da tutti i punti di vista.

Attivando Generate Cubemap, Unity cerca di creare una cubemap a partire dalla texture, ma questa deve essere creata appositamente. Per maggiori informazioni, si può guardare la reference sulle Cubemap.

Read/Write Enabled

Se attivo, permette agli script di modificare la texture in maniera permanente, utile ad esempio quando viene usata la webcam per catturare una texture o se vogliamo scrivere del testo su un’immagine, o modificarne i colori. Di fatto, questo è l’unico caso in cui Unity modifica l’asset originale ed è anche potenzialmente distruttivo. Se lo attivate e volete testare uno script, assicuratevi di avere una copia della texture che state modificando.

Import Type

Permette di trattare la texture come Normal Map o Lightmap, che sono formati diversi. Ne parliamo più in dettaglio in altre lezioni.

Bypass sRGB sampling

Determina se Unity debba o meno convertire lo spazio gamma dei colori da sRGB a lineare. È una impostazione che va modificata raramente, ma può tornare utile se vi servono i colori esatti della texture (perché magari sono indicizzati per rappresentare dei valori precisi) e non volete che Unity faccia la conversione.

Filter Mode

L’operazione di prendere un pixel dello schermo, ed andare a vedere a che texel corrisponde sulla texture, si chiama sampling (letteralmente campionare). Per quest’operazione possiamo definire come Unity deciderà di disegnare i pixel a schermo a partire dal file originale selezionando il tipo di filtro che verrà applicato in fase di sampling. Unity fornisce tre scelte di filtri: Point, Bilinear e Trilinear.

Filtro Descrizione

Point è l’equivalente di quello che in Photoshop viene chiamato Nearest Neighbour (o

“Vicino Più Prossimo” in italiano), ovvero campiona il pixel più vicino senza

cambiargli colore.

Page 101: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

100

Bilinear corrisponde a Bilineare, e si limita a fare la media fra i pixel vicini. È il metodo più

usato e dà buoni risultati quando lo scopo è avere una texture quanto più realistica

possibile.

Trilinear è un’evoluzione del precedente, che oltre a fare il lavoro di media fra i pixel vicini lo

fa anche fra i vari livelli di mip-map (vedi sezione apposita). È il migliore, ma

necessariamente anche leggermente più pesante da calcolare – nella maggior parte

dei casi Bilinear è sufficiente.

Ad esempio una texture molto piccola (16×16 texel), se vista molto grande a schermo risulterà in questo modo con un filtering di tipo Point:

Come si può vedere, la texture 16×16 viene trasformata e renderizzata in un’immagine alta fino a quasi 400 pixel. In questo caso, l’immagine non sgrana e i colori vengono preservati, ma possiamo notare che la texture ha dei bordi seghettati (ben visibili sui quadrati color ciano).

Vediamo il risultato con un filtering Bilinear:

Page 102: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

101

L’alone che vediamo intorno ai quadrati color ciano sono dei pixel “inventati” dal filtro, che in mancanza d’altro, fa una media fra il verde e il ciano per renderizzare la texture ad una risoluzione maggiore.

Nota: l’esempio di sopra utilizza una texture molto piccola a scopo dimostrativo. Di conseguenza, il filtro Point sembra dare un risultato più gradevole. In realtà, la texture 16×16 di cui sopra è stata pensata per avere bordi netti e seghettati (per un estetica pixel art in 3D), e quindi è pensata per il filtro Point. In generale però, il filtro Bilinear è quello che dà risultati migliori.

Per maggiori informazioni sui filtri, potete consultare questa pagina di Wikipedia.

Le Mip-Maps

Come detto, quando una texture viene renderizzata il motore grafico deve andare a campionare i texel dalla texture stessa. Se l’oggetto è molto piccolo a schermo, in generale viene caricata una texture inutilmente grande, sprecando memoria video.

Una tecnica comune è utilizzare le mip-maps: sono texture aggiuntive ad una risoluzione più bassa, che vengono utilizzate al posto dell’originale quando l’oggetto è lontano dalla telecamera. Queste texture vengono create automaticamente da Unity in fase di importazione, fino ad otto livelli (vale a dire che l’ultima sarà &frac18; dell’originale).

Ecco un esempio di mip-maps, con l’originale + 7 livelli progressivamente più piccoli (qui zoomati):

Page 103: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

102

Oltre al vantaggio di un’utilizzo di memoria inferiore, le mip-maps sono anche già ricampionate, permettendo di preservare i dettagli in maniera migliore rispetto al ricampionamento al volo che farebbe il motore di rendering, che creerebbe l’effetto di aliasing. Ad esempio:

In questo esempio in cui le mip-maps non sono attive, il cubo piccolo presenta degli artefatti grafici (le linee beige sono pixellose e presentano aliasing) perché il renderer deve ricampionare i dettagli al volo.

Page 104: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

103

In questa seconda immagine, il cubo piccolo è più “morbido” e le scanalature meno pronunciate.

Utilizzare una texture in un materiale

Dopo aver deciso come importare la texture, possiamo assegnarla ad un materiale. Creiamo un materiale nel pannello Project, oppure da Assets > Create > Material. A seconda del tipo di materiale (ovvero dello shader che utilizza) potremo assegnare una o più texture. Selezioniamo un semplice Diffuse.

Nell’esempio sopra, come texture Base abbiamo assegnato una texture ripetibile di mattoni.

Offset, Tiling e Wrap Mode

Page 105: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

104

I due parametri Tiling e Offset permettono di controllare come e quante volte la texture viene ripetuta. Sopra abbiamo l’esempio di Tiling ad 1, ovvero la texture viene ripetuta una sola volta su ogni faccia del quadrato.

Se selezioniamo il Tiling a 3, avremo questo risultato:

La texture viene ripetuta 3 volte in verticale e 3 volte in orizzontale nello stesso spazio di prima, e come risultato i mattoni risultano più piccoli.

Offset invece, permette di scegliere da dove iniziare a disegnare la texture: i valori vanno da 0 a 1, dove 0 indica sinistra o in alto, e 1 indica destra o giù.

Se Offset è 0 e 0, la texture viene disegnata semplicemente come abbiamo visto finora:

Page 106: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

105

Ma se impostiamo i valori di Offset su 0.3 e 0.3, la texture sarà disegnata a partire da un terzo della sua ampiezza. Nell’esempio di prima:

Quindi, Unity inizia a disegnare a partire da 0.3 lungo la texture, ed arrivato alla fine della texture, la ripete.

Se torniamo alle proprietà di importazione delle texture, possiamo ora provare ad impostare il Wrap Mode su Clamp piuttosto che Repeat (come visto fin’ora).

Cosa fa Clamp? Quando Unity disegna una texture ed arriva al bordo di questa, invece di ripetere i pixel ripartendo dal lato sinistro, continuerà a ripetere i pixel del bordo. Se mettiamo Offset su 0.3 e 0.3 e decidiamo di importare la texture con Clamp, questo è il risultato:

Page 107: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

106

Come si può vedere, la texture viene disegnata a partire da un terzo, ed arrivato alla fine del file, Unity continua a disegnare i pixel del bordo (ovvero un verde scuro).

Clamp è molto utile quando abbiamo ad esempio la texture di un marchio, e vogliamo che non venga ripetuto su una superficie, che per il resto avrà un colore a tinta unita.

28.Luci e ombre in unity In questa lezione parleremo di come vengono gestite luci ed ombre in Unity, e delle differenze fra i rendering path Forward e Deferred, e di come questi influenzano luci ed ombre. Come in molti programmi di grafica 3D, anche in Unity non esiste un trattamento davvero realistico della luce, ma il tutto viene simulato attraverso una serie di approssimazioni.

Nel mondo reale la luce non si ferma sulle superfici che colpisce ma rimbalza, si riflette, si rifrange nei liquidi. Tutti questi effetti citati non sono impossibili da ottenere, ma vanno simulati uno per uno ed a volte potrebbero essere molto costosi in termini di rendering, fino a risultare impossibili (ad esempio su mobile). Alcuni vengono ottenuti con gli shader (come la riflessione, la rifrazione), altri sono il prodotto di semplici GameObject che – coma accade spesso in Unity – fanno uso del componente Light.

La luce rimbalza anche sugli oggetti, illuminando di riflesso altri oggetti. Questo comportamento ricade in quello che viene definito Global Illumination, che è un processo di illuminazione molto complesso che può essere di tipo pre-calcolato (anche detto baked) o dinamico. Nel primo caso viene pre-calcolata prima che il gioco venga eseguito (un po’ come nei film in 3D), e ne parleremo in dettaglio quando menzioneremo le Lightmap. Nel secondo caso, viene calcolata in tempo reale mentre il gioco è in esecuzione… questa soluzione non è ancora disponibile in Unity 4 (mentre esiste in Unreal Engine), ma verrà in parte implementata in Unity 5.

Così come le luci, anche le ombre possono essere di tipo semplice, e dinamico (ovvero che si muovono) oppure baked. Ombre in movimento sono necessario in caso si voglia dare un’ombra ad un personaggio, ma questo processo è dispendioso (si pensi che le ombre dinamiche su mobile non erano presenti nelle prime versioni di Unity…).

Page 108: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

107

Le ombre statiche (baked), sono invece adatte agli ambienti in cui gli elementi non si muovono: possono essere pre-calcolate e disegnate in una texture che contiene anche le informazioni di luce (le Lightmap citate prima, appunto), e sono molto più morbide e dettagliate delle ombre dinamiche.

Esistono 4 tipi di luci in Unity: 3 dinamiche, ed una baked. Per creare una luce basta selezionare GameObject > Create Other > Directional Light (oppure una delle altre). Vediamole in dettaglio una ad una. Tralasceremo la Area Light, di cui parleremo nella lezione dedicata al Lightmapping.

La Directional Light

La Directional Light è una luce adatta a simulare un’illuminazione esterna, perché i suoi raggi sono paralleli e infiniti. Per questo motivo, questa luce influenza tutti gli oggetti nella scena indipedentemente dalla loro distanza, e non importa il suo posizionamento.

Inoltre, se le ombre non sono attive i suoi raggi non vengono bloccati dagli oggetti, quindi ci si potrebbe ritrovare nella strana situazione in cui un oggetto in una stanza chiusa risulti comunque illuminato su un lato da una Directional… è perfettamente normale.

Questo è come appare una Directional nella Scene View:

La Directional è indicata da un cilindro, con dei segmenti gialli che specificano la direzione dei raggi di luce. Come detto la posizione non è importante, e infatti nell’immagine possiamo vedere che anche se la Directional punta via dal cubo, questo è comunque illuminato dalla luce sul lato destro, così come è illuminato il piano.

La rotazione però lo è, e se ruotiamo la Directional sull’asse Y l’illuminazione cambia:

Page 109: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

108

Una Directional si presenta così nell’Inspector:

Chiaramente, Type ci permette di trasformare una Directional in un altro tipo di luce, mentre Color e Intensity ne controllano colore e luminosità. Di solito, una luce solare si attesta sullo 0.5 di Intensity, anche perché a questo vanno unite altre fonti di luce (magari di tipo Point), l’ambient, e i rimbalzi della Global Illumination (se presente).

Un Cookie (nelle luci) è una texture in bianco e nero, che funge da maschera, come se fosse stato applicato uno stencil davanti ad un proiettore. Se Cookie viene assegnato, Cookie size definisce quanto questo è grande.

Draw Halo permette di disegnare un bagliore intorno alla luce, usando una texture che sarà applicata in corrispondenza della fonte di luce. Per far funzionare Draw Halonon è sufficiente abilitarlo, ma bisogna anche assegnare la texture da usare andando in Edit > Render Settings.

Nota: se non vedete il bagliore, allontanate la luce dalla camera: Unity è tarato per non mostrare bagliori troppo vicini.

Le Flare, come i bagliori, sono effetti di luce che si applicano per simulare il cosiddetto Lens Flare, ovvero il bagliore derivato dalle lenti di obiettivi fotografici. Le Flare non sono semplici texture, ma un formato apposito gestito da Unity.

Per mostrare le Flare bisogna fare tre operazioni: per prima cosa importare il package che ne contiene un po’ (andando in Assets > Import Package > Light Flares), poi assegnarne una nella proprietà Flare del componente Light, ed infine configurarla nei Render Settings con Flare Strength e Flare Fade Speed.

Ecco come si presenta una Flare:

Page 110: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

109

Infine, per ora menzioniamo solo l’opzione Culling Mask: così come per gli oggetti Camera, si può dire alla luce di illuminare solo oggetti su alcuni layer. Selezionando i layer opportuni nella Culling Mask si possono creare effetti particolari, come ad esempio avere una luce che illumini la GUI (ammesso che questa sia in 3D!) che non è la stessa che illumina la scena.

Le Point Light

Le Point Light sono luci puntiformi, adatte a creare effetti come fiamme, torce medievali, fuochi da campo.

Come detto, è facile convertire una Directional in una Point Light semplicemente cambiandone il Type. In alternativa, possiamo crearne una da GameObject > Create Other > Point Light.

L’unica differenza importante con la Directional è che la Point Light ha un Range, che indica la distanza massima alla quale la Point Light illumina, espresso in unità di Unity. Il gizmo della luce si presenta così:

Come si può vedere, è una sfera che indica l’area di influenza. Sulla superficie ci sono 6 quadratini che possono essere trascinati per cambiarne l’ampiezza.

Page 111: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

110

Un oggetto che si trovi al bordo di questa sfera riceve la luce solo in maniera minima, mentre uno vicino al centro la riceve in pieno (ovvero quanto il valore di Intensity).

A causa delle sue caratteristiche, è importante dove viene piazzata una Point Light, ma è ininfluente la sua rotazione (esattamente il contrario della Directional).

Le Spotlight

Le luci Spot sono dei faretti, che proiettano un cono di luce che ha un’ampiezza (Spot Angle) ed una distanza massima (Range). Per questo motivo, è importante sia la posizione che la rotazione delle Spotlight.

Una Spotlight si presenta così:

Come si può vedere, proietta un cono di luce dai bordi morbidi, ed ha un gizmo che è anch’esso un cono. Muovendo i quattro quadratini collegati ai raggi si varia lo Spot Angle, muovendo quello centrale si cambia il Range.

Così come per la Point, gli oggetti vicino alla fine del cono saranno illuminati solo marginalmente, mentre quelli vicino alla fonte subiranno un’illuminazione maggiore (come il cubo nell’immagine).

Le ombre dinamiche

Così come le luci, anche le ombre sono un’approssimazione. Per le ombre Unity usa un metodo chiamato shadow mapping: in pratica, crea una mappa di profondità a partire dal punto di vista della luce (non della camera) e la usa per decidere dove proiettare le ombre.

Di conseguenza, la qualità delle ombre è molto influenzata dalla risoluzione di questa texture. La risoluzione si può settare in Edit > Project Settings > Quality Settings, modificando l’opzione Shadow Resolution.

Un altra opzione importante che si trova lì è Shadow Distance. Questo numero (espresso in unità di Unity) indica la distanza massima a cui vengono disegnate le ombre. Maggiore è la distanza massima, più Unity deve dividere la texture della shadow map per coprire tutta la distanza a cui si vedono le ombre.

Di conseguenza, avere ombre lontane comporta che tutte le ombre (anche quelle più vicine) saranno renderizzate ad una risoluzione minore. Molto spesso avere ombre troppo lontane non serve (il giocatore non le noterebbe comunque), quindi è meglio tenere questo valore più basso possibile per tenere al massimo la qualità delle ombre più vicine.

Page 112: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

111

Per ottenere le ombre, è sufficiente modificare lo Shadow Type nell’inspector di una luce Directional, scegliendo Hard Shadows (l’ombra è “dura”) o Soft Shadows (l’ombra ha dei bordi sfumati, chiaramente più pesante).

Rivedendo la luce creata ad inizio lezione, con le ombre risulterebbe così:

Come si può vedere, l’ombra risulta un po’ seghettata a causa della shadow map a bassa risoluzione.

Una volta abilitate le ombre, possiamo regolarle nell’Inspector della luce. Per le Hard Shadows, Strength ne indica l’opacità. Per risultati ottimali conviene rimanere su un valore 0.7 o qualcosa simile.

Bias serve ad evitare artefatti grafici nelle ombre. Ad esempio, quando Unity cerca di disegnare un’ombra proveniente da una luce Directional che punta verso il basso, potrebbero nascere artefatti grafici sulle facce molto verticali degli oggetti, dovuti alle imprecisioni di shadow map a bassa risoluzione.

Un esempio concreto:

Page 113: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

112

In quest’immagine, un cubo viene illuminato da una luce quasi perpendicolare (ruotata di 108.1 gradi sull’asse X). La luce ha Hard Shadows con Bias a 0. Sul lato ‘davanti’, appare un artefatto grafico, una riga più scura sull’edge superiore.

Se portiamo il bias a 0.04, l’artefatto scompare:

Questo perché il bias è un distacco fra l’ombra e l’edge che la proietta. Un distacco minimo (fra 0.01 e 0.05, di solito) che l’utente in generale non noterà, ma che può contribuire ad evitare brutture come quella sopra.

Altre impostazioni delle ombre sono Resolution (lo stesso che si trova inQualitySettings, solo che qui possiamo specificarlo per ogni luce) e, nel caso di ombre Soft, i parametri Softness e Softness Fade, che regolano la morbidezza dei bordi delle ombre (conviene fare un po’ di prove per vedere la resa).

Niente ombre su mobile?

Bisogna ricordare che le ombre sono un effetto ‘molto’ pesante, spesso impossibile da avere su piattaforme come quelle mobile (ovvero, l’effetto funziona, ma riduce il framerate drasticamente). In questi casi, è meglio evitare di avere ombre in realtime per tutti gli oggetti e passare ad ombre ed illuminazione baked, come vedremo meglio nella lezione dedicata al Lightmapping.

Debuggare le ombre

Può capitare di attivare le ombre, ma di non vederle. Questo può dipendere da una miriade di fattori, alcuni già citati nella lezione, altri no. Vediamo i più comuni in una comoda checklist:

Verificare che le ombre siano attive nell’opzione Shadows in Edit > Project Settings >

Quality;

Verificare che la Shadow Distance sia sufficiente a coprire la distanza fra la camera e le

ombre che si vorrebbero vedere;

Verificare che la luce sia una Directional, e che sia l’unica con le ombre se parliamo di forward

rendering (vedi sotto);

Page 114: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

113

Verificare che il materiale (quindi lo shader) che dovrebbe ricevere l’ombra supporti le ombre

(alcuni shader cartoon non lo fanno ad esempio);

Verificare che la Strength sia maggiore di 0;

Verificare che l’oggetto che dovrebbe proiettare l’ombra non utilizzi uno shader della

famiglia Trasparent.

Forward e Deferred rendering

Unity mette a disposizione tre modi di renderizzare la scena: vertex, forward edeferred. Mentre vertex è un metodo ottimizzato per vecchi hardware, forward e deferred sono due metodi attuali ed ognuno con i suoi pro e i suoi contro.

Ad esempio, è possibile avere anti-aliasing in forward rendering ma non in deferred. Nel deferred non si possono avere oggetti semitrasparenti.

I punti di forza del deferred però sono sull’illuminazione. Innanzitutto possiamo avere luci virtualmente infinite, laddove il forward rendering ha un numero massimo di luci “pixel”, che va impostato nei Quality Settings citati sopra, sotto Pixel Light Count. Questo numero indica le luci da calcolare al pixel, tutte le altre sono da calcolare al vertice (quindi meno precise e realistiche). Il problema è che spesso su piattaforme mobile è meglio tenere questo numero a uno, massimo due.

Nel deferred rendering invece, possiamo avere più fonti di luce che influenzano tutte gli oggetti nel loro range. Questo apre infinite possibilità di illuminazione dinamica, con torcie, fari di automobile, esplosioni, ecc. che invece di avere un “effetto di luce” finto, possono essere delle vere e proprie luci che influenzano gli oggetti circostanti.

Inoltre, nel forward rendering solo una luce Directional può avere le ombre. Nel deferred tutti i tipi di luce (Spot, Directional, Point) possono generare ombre, ed il numero di luci che proiettano ombre non è limitato a uno.

Di conseguenza, il deferred rendering è preferibile quando disponibile (non è disponibile su tutte le piattaforme, e richiede Unity Pro), a meno che non sia necessario avere corpi semitrasparenti o anti-aliasing. Per maggiori dettagli sui rendering path,vedere qui.

29.Baking e lightmaps Una lightmap, come dice il nome, è una texture map che contiene i valori della luce e delle ombre proiettate sugli oggetti della scena. Le lightmap si sommano alle texture diffuse moltiplicandone i valori RGB, e definendo il colore finale dell’oggetto. Di fatto, le lightmap sono spesso in bianco e nero – a meno che non ci siano luci colorate in scena o oggetti colorati che riflettono la luce.

In Unity le lightmap vengono create con un software incluso nell’engine stesso, di nomeBeast. Quando vogliamo renderizzare le luci e le ombre sulle lightmap, possiamo lanciare un’operazione chiamata bake (ovvero “cuocere al forno”) dal pannello Lightmap (si può aprirlo partendo da Window > Lightmapping nel menu in alto). A questo punto Beast raccoglie tutte le informazioni sulla scena, sulle geometrie e sulle luci, e fa i suoi calcoli, producendo infine le lightmap che verranno automaticamente importate nel progetto ed assegnate agli oggetti.

Una cosa da ricordare è che il baking è viene effettuato in fase di creazione del gioco e non in tempo reale durante il gameplay, quindi è utile solo per quegli oggetti che non si muovono durante il gioco, come strade, edifici, sfondi, lampioni, ecc.

Page 115: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

114

Prima di fare un baking, la scena necessita di essere preparata. Per prima cosa, tutti gli oggetti che devono avere una lightmap vanno segnati come Static, spuntando la casellina in alto a destra nell’Inspector. L’oggetto non deve necessariamente essere statico in tutti i sensi, basta che sia Lightmap Static:

Questo perché – ad esempio – potremmo avere una ‘nave volante’ (come in Super Mario Bros 3!) per la quale di fatto vogliamo creare le lightmap, ma che si muove nel cielo. Non deve quindi essere Batching Static, altrimenti verrà unita alle altre geometrie ai fini dell’ottimizzazione e non si potrà muovere.

Inoltre, gli oggetti da lightmappare devono avere un set di coordinate UV per la lightmap (che molto spesso non coincide con le UV delle texture diffuse o normal). Se l’oggetto è un .fbx, .obj, ecc. importato, nelle proprietà di importazione basterà spuntareGenerate Lightmap UVs, e Unity le creerà per voi (sostituendole a qualunque set UV2 che trovi).

A questo punto, basterà mettere una luce in scena e possiamo iniziare il bake. Se si vogliono le ombre, basta abilitarle dall’Inspector avendo selezionato la luce. Se attive però, non vengono più regolate nell’Inspector come abbiamo visto nella lezione precedente (quelli sono i valori delle ombre realtime), ma direttamente nel pannellinoLightmapping.

Il baking

Attenzione: il Lightmapping è una di quelle feature che differisce un po’ fra la versione gratuita di Unity e quella Pro. In questa lezione andremo fino in fondo, facendo riferimento alla versione Pro ai fini didattici. I concetti ed i consigli valgono in ogni caso anche per la versione gratuita.

Per cominciare un bake, apriamo il pannellino Lightmapping (Window > Lightmapping nel menu in alto). Questi si divide in 3 sotto pannelli: Object, in cui si possono regolare i parametri dei singoli oggetti coinvolti nel bake; Bake, dove possiamo regolare la qualità del bake e lanciare l’operazione vera e propria; e Maps, in cui possiamo ispezionare le texture map prodotte.

Il pannello Bake si presenta così:

Page 116: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

115

Il primo valore Mode si riferisce al tipo di lightmapping che applichiamo, in questo casoSingle. Esistono anche Dual, e Directional, ne parleremo a breve.

Quality indica la qualità del risultato: è importante ricordare che trovare i parametri giusti per il

lightmapping può essere lungo e complesso, quindi è meglio tenere la Quality su Low in un primo

momento mentre si fanno prove, e poi usare High per produrre le lightmap definitive.

Bounces indica il numero di rimbalzi che la luce può fare sugli oggetti, creando la cosiddetta Global Illumination (ovvero un illuminazione che non arriva solo dalla fonte di luce, ma anche dagli oggetti vicini… un’illuminazione globale). Nella realtà, sarebbero infiniti (finché non ‘muore’), ma ai nostri fini dovremmo definire un numero da 0 a 4. Più rimbalzi più le lightmap saranno di qualità, ma più tempo ci vorrà per calcolarle. I Bounces sono una feature di Unity Pro.

Quando il pannellino Lightmapping è aperto, nella Scene View compare un pannello fluttuante in basso a destra:

Qui si trovano alcune opzioni minori, che in generale non influenzano il bake ma solo la visualizzazione delle Lightmap in fase di lavoro. Con Use Lightmaps ad esempio, possiamo

Page 117: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

116

disabilitare temporaneamente le mappe per poter vedere la scena con un’illuminazione dinamica, senza effettivamente rimuoverle.

Esempi di lightmaps

Creiamo una scena di prova per fare dei test. Creiamo due cubi colorati ed un piano bianco, e mettiamo una luce Directional bianca con un valore di 0.5 di Intensity ed attiviamo le ombre (non importa di che tipo). Prima di fare il bake, avendo solo le luci dinamiche otterremo un risultato simile:

Come è possibile notare, la luce illumina i cubi e proietta un’ombra, ma sul piano bianco non c’è traccia di riflessi di luce, ogni lato dei cubi è di un colore netto senza sbavature, il tutto è molto asettico e finto.

Se attiviamo Show Resolution nel pannellino grigio nella Scene View, la scena sarà coperta da una scacchiera: questa indica qual è la risoluzione della Lightmap una volta applicata al modello. Una trama più fitta vuol dire più ricchezza di dettagli, ma anche che verranno usate più lightmap per coprire tutta la scena. A seconda dei settaggi, alcuni modelli potrebbero avere caselle più grandi o più piccole di altri.

Per variare la dimensione di questa scacchiera, bisogna utilizzare il valore di Resolutionche si trova come penultima opzione nel sotto-pannello Bake all’interno dell’Inspector. In questo caso, l’abbiamo impostato su 30 texels per world unit (ovvero, un cubo grande 1 unità userà 30×30 pixel per lato sulla texture della Lightmap):

Page 118: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

117

Non c’è bisogno che la risoluzione della lightmap sia altissima, ma come sempre dipende dal tipo di gioco. Per giochi in cui il terreno è visto da un punto di vista alto, anche valori come 10 o 5 potrebbero andare bene. Per altri generi (soprattutto FPS al chiuso) saranno necessari anche 20, 30 o anche 50 texel per un risultato dignitoso.

Disattiviamo Show Resolution, e lanciamo finalmente un bake premendo sul bottone Bake Scene. Se gli oggetti non sono impostati correttamente (non statici), Unity tirerà fuori un errore in console. Altrimenti, partirà correttamente il bake, con una barra di caricamento in basso a destra:

Fintanto che la barra carica, possiamo anche continuare a lavorare sulla scena, ma non possiamo salvare né mettere in Play il gioco. Una volta finito il bake, Unity importerà le lightmaps nel progetto in un formato .exr, in una cartella che ha lo stesso nome della scena per cui sono state create (perciò la scena va salvata prima!). Cancellando la cartella (o premendo il tasto Clear) le lightmap vengono rimosse e la scena torna ad un’illuminazione dinamica. Se vogliamo vedere la scena con illuminazione dinamica solo temporaneamente, come detto, possiamo usare l’opzione Use Lightmaps nel pannellino nella Scene View per spegnere e riaccendere le lightmap.

Il risultato del primo bake in modalità Single Lightmaps:

Page 119: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

118

Il risultato non è male, più interessante dell’illuminazione realtime, sebbene le ombre abbiano un bordo seghettato (è colpa del settaggio Low su Quality). Questo bake ha richiesto 11 secondi.

Aumentiamo la Ambient Occlusion da 0 a 0.7. La tecnica dell’Ambient Occlusion non tiene in conto le luci in scena, ma quanto una superficie è “nascosta” dagli altri oggetti, e permette di avere parti più scure negli anfratti e dove gli oggetti sono molto ravvicinati:

Attivando l’Ambient Occlusion, i tempi di rendering salgono a 17 secondi ma otteniamo un interessante effetto alla base dei cubi.

Le ombre sono ancora molto nere, quindi attiviamo i Bounces. Questo ci dà accesso anche alla Skylight, ovvero una luce globale che viene “dal cielo”, e che di default impostiamo su un azzurrino a 0.3 di Intensity. Questo ci permetterà di creare un effetto da esterno, schiarendo tutte le ombre. Con 1 bounce, i tempi di rendering vanno a 24 secondi:

Page 120: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

119

2 Bounces, 22 secondi:

4 Bounce, 23 secondi:

Page 121: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

120

Si può notare che fra 2 e 4 bounce non ci sono molte differenze, né di qualità né di tempo. Questo probabilmente perché essendo la scena vuota, i raggi rimbalzano via prima di effettuare 4 rimbalzi.

Essendo contenti del risultato, possiamo attivare la High Quality:

I tempi di rendering salgono a 32 secondi, ma il risultato è molto migliore.

Altre modalità: Directional Lightmaps e Dual Lightmaps

Single Lightmaps non è l’unica modalità con cui si può fare il bake. Selezionando Directional Lightmaps in Mode, Unity produrrà due set di lightmaps (quindi il numero di texture verrà duplicato, tenetene conto!).

Il primo set assomiglierà molto a quello prodotto sin’ora, ovvero con le informazioni di intensità della luce. Nel secondo set, Unity incorporerà le informazioni sulla direzione da cui proviene la luce. Così facendo, è possibile avere materiali con specular o normal maps. In aggiunta, Unity starà attento a non calcolare la seconda Lightmap per gli oggetti che non

Page 122: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

121

hanno bisogno di specular o normal maps (quindi attenti ai materiali, o meglio agli shader, che utilizzate!).

Come contro, c’è che bisogna gestire un secondo set di immagini (quindi serve più ram per queste texture, che spesso sono grandi) e che i render durano di più, e inoltre a volte la compressione di queste texture fa sì che sgranino, producento risultati non ottimali. Se però usate i rimbalzi di luce, il gioco potrebbe valere la candela. Vediamo un render con le Directional Lightmaps:

Qui i tempi di rendering sono saliti a 52 secondi. Un bell’aumento dai 32 che avevamo con le Single!

In ultimo, le Dual Lightmaps. Come dice il nome, Unity crea due set di lightmap, uno “lontano” ed uno “vicino”. Per decidere a che distanza usare uno e a che distanza l’altro, possiamo regolare il valore di Shadow Distance nel box nella Scene View.

Questi due set hanno diverse funzioni: nel set lontano, Unity immagazzina le informazioni di luce ed ombre in maniera liscia (ovvero non ne sa la direzione, esattamente come per le Single Lightmaps), che impedisce di avere riflessi, normal e specular. Questo set viene usato per gli oggetti lontani, che non ricevono ombre dinamiche.

Nel set vicino, Unity immagazzina le informazioni complete sulla luce (come per le lightmap lontane), ma solo per le luci che sono impostate come Baked Only nella proprietà Ligthmapping (in basso nell’Inspector). Per quelle impostate come Auto, immagazzina solo la luce indiretta (rimbalzi), ma lascia che siano le luci realtime a definire ombre, specular e normal.

Detto in altri termini, quando una luce è entro la distanza di Shadow Distance, Unity la tratta come fosse realtime, usando le ombre realtime, e calcolando i riflessi e le normal ogni frame. A questi aggiunge le lightmap vicine, per avere colori sfumati e i rimbalzi di luce. Appena esce dal campo d’azione, Unity la rende baked, usando la lightmap lontana anche per le ombre, e disattivando normal e specular (perché questa lightmap, al contrario del caso delle Directional Lightmaps, non contiene informazioni su da dove provenga la luce).

In questo modo si possono avere delle buone luci ed ombre, e da vicino avere personaggi (magari fermi sul posto, ma con un ciclo animato di idle) o bandiere con ombre dinamiche, con normal maps e specular.

Ad esempio, attivando le Dual Lightmaps avremo questo risultato:

Page 123: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

122

Come si può vedere, le ombre dei cubi sono peggio di prima (perché sono realtime), ma abbiamo il vantaggio che se li alziamo da terra l’ombra si muove con loro. Ciononostante, sui bordi in basso dei cubi possiamo vedere un accenno di global illumination che li rende più interessanti dell’esempio completamente realtime che abbiamo fatto ad inizio lezione.

Le Dual Lightmaps in Unity funzionano solo con il rendering deferred. Per farle funzionare nel forward, bisogna scrivere degli shader appositi e spuntare la casellinaUse Dual Lightmaps in Forward Rendering nel pannello Lightmapping.

A prescinedere dalla tecnica usata, il lightmapping permette di avere risultati molto belli se le luci sono usate con sapienza. Inoltre, permette di avere luci ed ombre multiple dove non potremmo averle (ovvero nel forward rendering), ed avere ombre anche per luci Point e Spot (non possibili in realtime). Un esempio banale con qualche altra luce colorata:

Nella prossima lezione vedremo altre informazioni sul lightmapping, e sulla soluzione per far funzionare la Global Illumination con oggetti in movimento: le Light Probe.

Page 124: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

123

30.Light probe, illuminazione e movimento In questa lezione vediamo la tecnica delle Light Probe, un metodo per avere unaGlobal Illumination sugli oggetti in movimento. A seguire, alcune opzioni extra del Lightmapping.

Le Light Probe

Visto che le lightmap sono utili solo per oggetti che non si muovono, serve una tecnica per approssimare la global illumination per oggetti in movimento. Le light probe assolvono a questo compito: non contengono tutte le informazioni di luce (e soprattutto di ombre) delle lightmap, ma sono meno esose rispetto al calcolare la luce su un oggetto in realtime.

Le probe sono, come dice il nome, dei campionamenti della luce in alcuni punti della scena. Appaiono in scena come una sfera, sulla cui superficie viene salvata la luce che proviene dalle parti circostanti. Gli oggetti che fanno uso di light probe usano queste sfere per calcolare il loro colore, invece di leggerlo dalle luci stesse. Quando si trovano in mezzo a due o più sfere, viene fatta una media fra le sfere intorno all’oggetto stesso.

Ad esempio, in questa scena abbiamo 3 luci Point colorate ed una Directional bianca in alto, tutte con ombre (quindi è necessario usare il Deferred Rendering per avere le ombre). Come geometrie, è presente una serie di parallelepipedi ed un piano tutti statici, e per questi basta usare le lightmap.

Abbiamo poi una capsula che è mobile, quindi la lightmap non va bene. Se lasciamo che la capsula sia illuminata in realtime, si vedrà così:

Usando le light probes, possiamo ottenere un effetto simile:

Page 125: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

124

Come si può notare, la capsula risulta meglio integrata nel contesto e riproduce più fedelmente la situazione complessa di luce di questa scena.

Per utilizzare le light probes, serve un “oggetto contenitore”, ovvero un GameObject vuoto. Andiamo su GameObject > Create Empty, il posizionamento di questo non è importante, ma conviene metterlo al centro della scena per praticità. Successivamente, aggiungiamo un componente di nome Light Probe Group. Viene creato il componente, ma ancora non ci sono probe in scena.

Cliccando su Add Probe viene aggiunta una nuova probe, che in scena appare come una sfera gialla, che possiamo spostare mediante il gizmo traslazione – come un normale GameObject. Si può anche fare click per selezionarla, o premere F per centrare la vista su di essa.

Continuando con Add Probe possiamo aggiungere le altre probe necessarie a sondare l’illuminazione della scena. Per decidere dove piazzare le probe, bisogna seguire due regole. La prima è che le probe creano delle strutture piramidali, che dividono lo spazio in tante piramidi. A partire da queste poi Unity farà l’interpolazione, usando la posizione dell’oggetto per decidere in quale piramide questo si trovi. Ad esempio nel nostro caso:

Page 126: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

125

Queste probe formano una piramide, una vicino ad ogni luce colorata, ed una in alto. La probe in alto non fa il sampling di nessuna condizione di luce in particolare, ma è utile per creare la struttura a piramide.

Una volta sistemate le probe, possiamo andare nel pannello Lightmapping, e premere il tasto Bake Probes (è lo stesso del tasto Bake Scene, si trova premendo sul triangolino). Il processo è molto rapido, e produce un nuovo asset nella cartella della scena di tipo .asset:

Questo viene automaticamente assegnato come probe della scena. Come ultima operazione, dobbiamo fare in modo che la capsula utilizzi le probe. Per fare ciò, la selezioniamo e nell’Inspector del suo Mesh Renderer spuntiamo l’opzione Use Light Probes.

Come si può vedere sotto, la capsula si trova principalmente vicino alle probe della luce rossa e di quella verde, e perciò prende l’illuminazione da queste due facendo una media:

Page 127: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

126

Anche muovendo la capsula ci accorgeremo che l’illuminazione – pur essendo di qualità quasi da Global Illumination – viene aggirnata in tempo reale e cambia a seconda della posizione della capsula.

Il pannellino Object e le opzioni per oggetto

Proseguendo il discorso delle lightmap intrapreso nella lezione prima, vediamo qualche altra opzione utile.

Nel sotto-pannello Object incluso nel pannello Lightmapping è possibile regolare le opzioni di baking per i singoli oggetti. Il pannello presenta un filtro: selezionando ad esempio Renderers, potremo vedere nella Hierarchy tutti gli oggetti che verranno forniti di una lightmap. Se qualcuno di questi non è Lightmap Static, possiamo modificarlo rapidamente da qui.

Quando viene fatto il baking, non viene creata una lightmap per ogni singolo oggetto, ma più oggetti vengono raggruppati sulla stessa texture con una tecnica che si chiama atlasing.

I valori di Scale in Lightmap ed Atlas controllano come Unity posiziona le coordinate UV dei singoli oggetti sulle lightmap: ad esempio se vogliamo che un oggetto abbia meno spazio sulla lightmap (perché non viene mai visto da vicino, o ci interessa meno di come si vedono le ombre su di esso) possiamo ridurre Scale a 0.5 o meno. Così facendo, Unity userà la metà dello spazio sulla lightmap per quel dato oggetto. Stesso discorso al contrario, aumentando il numero aumenterà lo spazio, ma solo fino ad un limite: se andiamo oltre, comparirà un messaggio che dice “Object’s size in lightmap has reached the max atlas size.”, ovvero che Unity sta usando un’intera lightmap solo per questo oggetto, e che non possiamo farlo più grande.

I parametri di Atlas invece vengono impostati da Unity, e determinano su quale lightmap si trova un oggetto, e come è posizionato su di essa. Ad esempio:

Page 128: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

127

In questo caso, l’oggetto si trova sulla prima lightmap (quella con index 0), occupa un quadrato il cui lato è grande lo 0.07 della lightmap (i valori di Tiling), ed è posizionato circa a 0.2 sulla X ed a -0.0 sulla Y.

Questi valori di solito non vanno modificati manualmente. L’unico caso potrebbe essere quello in cui la lightmap venga creata manualmente al di fuori di Unity, e si voglia far combaciare le UV con la sezione di lightmap creata, ma in questo caso bisognerà probabilmente disabilitare Generate Lightmap UVs nell’importazione del modello e fare l’unwrapping del secondo set di coordinate a mano nel proprio package 3D.

Per le luci invece, questo sotto-pannello presenta un paio di opzioni che sono legate all’Inspector della luce stessa (ovvero Lightmapping, il colore e l’intensità), ed alcuni valori che controllano la qualità delle ombre. Da notare che le ombre baked sono sempre morbide, solo quelle realtime possono essere Hard. Quindi un valore On (Realtime: Hard Shadow) vuol dire che la luce avrà ombre dure solo quando diventa realtime, come nel caso di luci vicine alla camera nelle Dual Lightmaps.

Il pannellino Maps

Il terzo sotto-pannellino di Lightmapping, Maps, permette di revisionare le lightmap create. Array Size determina il numero di lightmap (di nuovo, è un parametro che non va toccato a meno che non si stiano creando le lightmap al di fuori di Unity).

Il valore di Compressed permette di decidere se le lightmap sono Compressed, o Truecolor. In alternativa, si può cliccare su una delle texture in basso per localizzarla all’interno del progetto, ed impostarne la compressione a mano. Come sempre, texture più compresse sono più leggere ma possono presentare artefatti anche molto visibili. Con questo metodo si potrebbe anche impostare le dimensioni della texture: portando le lightmap da 1024 a 512 di lato, ognuna diventa un quarto di quanto era prima… a quel punto, si potrebbe valutare l’idea di usarle non compresse per una migliore resa visiva.

In questo pannellino troviamo anche, in alto, le light probes: nella prima casella è infatti linkato l’asset delle light probe prodotto a seguito del bake delle probe, e possiamo scollegarlo (selezionandolo e premendo Delete) o sostituirne un altro.

31.Sistemi particellari In questa lezione analizzeremo i sistemi particellari, ovvero i componenti utilizzati per rappresentare oggetti complessi e spesso amorfi (come liquidi, fiamme, fumo, o altre entità fluide o gassose) per cui le mesh 3D non sono adatte.

Page 129: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

128

Per sistemi particellari intendiamo dei generatori che producono una quantità di particelle, le quali non sono veri e propri gameObject ma solo degli effetti grafici. Utilizzare delle entità semplificate fa sì che i sistemi particellari possano generare, gestire ed animare molte più particelle di quanto si potrebbe fare utilizzando un oggetto per ognuna di esse. Infatti, i sistemi particellari in Unity possono generare centinaia di particelle, muoverle nello spazio, cambiarne la dimensione e il colore, ed in più legare tutti questi valori al tempo dalla’nascita’ della singola particella, o renderli casuali.

In genere, le particelle sono billboard, ovvero dei quad (fatti da due triangoli) che hanno una texture (spesso con canale alpha) e guardano sempre verso la telecamera. In realtà, è possibile anche orientarle in maniera diversa (rivolte verso l’alto, o in altri modi), ma difficilmente torneranno utili altri orientamenti. Oltre alle billboard, si possono usare anche mesh 3D come particelle… ma le performance diminuiranno drasticamente rispetto al primo metodo.

Il componente Particle System

Vediamo come creare un sistema particellare da zero: basterà usare GameObject > Create Other > Particle System. Avremo un nuovo GameObject con un solo componente (oltre al Transform) che si occuperà della generazione, del rendering, e dell’animazione delle particelle.

Nota: Unity possiede al momento due tipi di sistemi particellari, uno molto vecchio (che rimane per retrocompatibilità, detto “legacy”) ed uno nuovo (il cui nome in codice è Shuriken). Il vecchio non viene quasi mai usato, né si trovano più risorse in giro, per cui non lo tratteremo. I vecchi componenti si trovano sotto Components > Effects > Legacy Particles.

È importante ricordare che il nuovo sistema Shuriken risiede tutto in un solo componente (chiamato appunto Particle System) e che non bisogna mischiare componenti del sistema legacy con il componente di Shuriken.

Il sistema particellare creato apparirà come una sorta di fontana di palline bianche (la texture di default) e, se selezionato, sarà animato e mostrerà una finestrella nell’angolo in basso a destra nella Scene View:

Page 130: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

129

Questa finestra fornisce un controllo sul sistema particellare, ma solo in fase di editing: permette di fermarlo, riavviarlo, o di farlo andare più o meno veloce. Ricordiamo però che non sono opzioni che andranno a finire nel gioco: servono solo a debuggare eventuali errori di impostazione nel sistema particellare.

Il componente Particle System appare così nell’Inspector:

Page 131: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

130

È un componente più complesso degli altri, formato da tanti moduli che possono essere abilitati o spenti a seconda della necessità. Alcuni di questi però sono fondamentali, come:

Emission, che regola quante particelle vengono emesse nel tempo;

Shape, che indica la forma dell’emettitore);

Renderer, che regola come le particelle vengono disegnate a schermo;

oltre che naturalmente al primo blocco che contiene le proprietà di base del sistema particellare.

Analizzando questo blocco per primo, soffermiamoci sul tempo: i sistemi particellari possono funzionare in maniera ciclica, o essere one-shot (ovvero essere riprodotti una

Page 132: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

131

volta sola, come le esplosioni). Chiaramente, questi valori (come anche altri) possono essere modificati via codice a runtime, ma per ora parleremo solo di quello che si può fare regolando il sistema particellare dall’Inspector.

Duration indica la durata di “un ciclo” di emissione (di default 5 secondi). Se Looping non è spuntato, dopo 5 secondi il sistema smetterà di emettere particelle. Se lo è, vedremo un flusso continuo ma ogni 5 secondi avverranno gli eventi iniziali (vedi dopo).Prewarm è molto importante: abilitandolo, il sistema particellare verrà simulato per qualche secondo prima che la scena venga avviata, così che all’avvio del gioco avremo già una serie di particelle in movimento. Questo è molto utile per elementi come cascate, fuochi o torce, dove è importante vedere l’effetto completo del sistema sin dal primo frame in cui l’elemento appare in scena – e non vogliamo vedere il sistema particellare’avviarsi’. Non è possibile attivare Prewarm se il sistema particellare non è di tipo loop.

Start Delay indica il ritardo nell’avvio, e non è abilitabile se Prewarm è attivo… ed è ovvio, perché Prewarm è come se fosse un delay negativo (ricordiamo che l’avvio dell sistema particellare viene anticipato, non posticipato)!

Play on Awake indica se il sistema viene messo in play automaticamente all’avvio del gioco. Attenzione: se non è spuntato, servirà uno script che lo metta in play conParticleSystem.Play().

Animare le particelle

Oltre alle opzioni relative al sistema, esistono una serie di opzioni che caratterizzano le singole particelle. Start Delay, Speed, Lifetime, Size, Color e Rotation sono tutte indicative della condizione di partenza delle singole particelle.

C’è da dire però che assegnare lo stesso valore ad ogni particella produrrebbe risultati monotoni. è per questo che Unity mette a disposizione alcune possibilità alternative per aggiungere varietà: oltre a valori fissi (Constant) o totalmente random (Random Between two Constants), è possibile distribuire i valori lungo una curva (Curves) o casualmente fra due curve (Random Between two Curves). In questo caso ad esempio, stiamo editando il valore della curva per Start Rotation:

Page 133: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

132

Quando modifichiamo una curva, in basso appare il Curve Editor. è un tool semplice, in cui una serie di punti di controllo definiscono il profilo di una curva, completi di’maniglie’ che fanno da tangenti ai punti e che permettono di modificare i punti di ingresso e di uscita della curva (un po’ come quando si disegna in vettoriale in programmi come Illustrator, Photoshop, Freehand, ecc.):

Per creare un nuovo punto di controllo basta fare doppio click sulla curva, per muoverlo basta trascinarlo, e per cancellarlo basta cliccare col destro sul punto e selezionare Delete Key.

Page 134: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

133

Il Curve Editor è uno strumento che ricorre spesso in Unity, non solo nei sistemi particellari ma anche in altri componenti chiave, come durante la creazione di animazioni.

Come si interpreta una curva? Come si può vedere dall’immagine di cui sopra – relativa al parametro Start Rotation (ovvero la rotazione di partenza di ogni particella), la curva risiede su un grafico a due assi. Quello orizzontale è relativo al tempo durante il loop (controllato quindi tramite il parametro Duration, che qui vale 5 secondi), mentre quello verticale è il parametro stesso (in questo caso Start Rotation, che può valere da -180 a 180, anche se possiamo modificare il valore massimo).

Insomma, interpretando la curva di cui sopra, abbiamo in pratica detto ad Unity di non ruotare le particelle generate all’inizio (tempo 0) e, man mano che il tempo passa, di generarle ruotate sempre di più fino ad un valore di circa 150 gradi al secondo 3. Da qui in poi (cioè nella parte piatta della curva), tutte le particelle saranno ruotate di 150 gradi fino alla fine del loop, per poi ricominciare da 0 gradi.

Le curve sono uno strumento semplice ma potente che, se usato bene, può dare risultati sorprendenti e molto creativi.

Altre proprietà di base

Il modulo Emission regola la quantità di particelle emessa. Se è di tipo Time, il valore Rate indica quante particelle vengono emesse ogni secondo. Se è di tipo Distance, il sistema emetterà particelle solo quando si muove. In questo caso, Rate indica quante particelle vengono emesse quando il sistema particellare si muove di un unità. è evidente che se il sistema particellare non si muove, non verranno emesse particelle… il che lo rende adatto a tubi di scappamento, o scie di oggetti in movimento.

Quando l’emissione è di tipo Time, si rendono disponibili i Bursts, ovvero delle emissioni improvvise di più particelle alla volta. Premendo sul + si possono aggiungere’esplosioni’, decidendo in che momento avvengono e quante particelle vengono emesse, fino ad un massimo di 4. Chiaramente i tempi si riferiscono al loop del sistema: un sistema con Duration 5 ed un solo Burst a 0 ad esempio, partirà con un esplosione di particelle, e la ripeterà ogni 5 secondi.

Da notare che i Bursts non devono per forza emettere centinaia di nuove particelle: si possono programmare 2-3 bursts con tempi non regolari e poche particelle, per rendere meno evidente il loop all’occhio umano e quindi più naturale il sistema particellare.

Altro modulo fondamentale, Shape indica la forma dell’emitter, ovvero l’area dove le particelle vengono generate e la direzioni in cui vengono sparate (che funziona in tandem con il parametro Start Speed). Senza andare nel dettaglio, esistono 4 forme di base regolabili con diversi parameteri, più la possibilità di usare una mesh come emettitore.

In quest’ultimo caso, si consiglia di non usare mesh eccessivamente complesse come numero di triangoli, e con una distribuzione regolare di questi: se così non fosse, si noterebbero più particelle generate dove la triangolazione è più fitta, e meno altrove.

Altri Moduli del Sistema Particellare

Sotto ai moduli di base si trovano una serie di moduli opzionali che di default sono disattivi, ma che possono essere abilitati per dare più vita al sistema tramite le spunte vicino al titolo di ognuno di essi. Molti si commentano da soli, ma vediamoli a grandi linee.

Page 135: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

134

Alcuni moduli sono over Lifetime. Questo vuol dire che agiscono durante il corso della vita della particella, indipendentemente da quanto questa sia lunga (le particelle possono avere vita diversa se Start Lifetime non è di tipo Constant!).

Ad esempio, Color over Lifetime permette di definire un gradient, e la particella cambierà di colore partendo dal lato sinistro (alla sua nascita) fino ad arrivare a quello destro (al momento della distruzione).

Attenzione: come per Start Color, anche Color over Lifetime funziona solo se il materiale in uso per il sistema particellare è un tipo Particle. Questo perché queste proprietà non agiscono sul parametro _Color dello shader ma su quello _Tint, che è tipico degli shader della famiglia Particle.

Nell’editare il gradient si aprirà il Gradient Editor:

Page 136: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

135

Il Gradient Editor presenta una fascia in alto, dove è possibile inserire una serie di marker. Nell’immagine in alto ne abbiamo 6: 3 in alto, che regolano la trasparenza del gradient, e tre in basso che ne regolano il colore. Come per le maniglie nelle curve, per aggiungere marker basta fare doppio click sul bordo alto o basso della fascia di colore, e trascinarli via (in alto o in basso) per rimuoverli.

Il gradient in figura ha una sfumatura che va dal nero passando per il rosso (quello selezionato, al 50%), fino al bianco. Come alpha invece, abbiamo messo 3 marker: gli estremi sono al 100% di opacità, mentre quello centrale è semitrasparente, come viene evidenziato dalla trama a scacchiera che compare nel mezzo. In questo caso quindi, la particella nasce nero pieno, dopo un po’ diventa semitrasparente e rossa, per poi andare a morire bianca in piena opacità.

Possiamo definire quanti marker vogliamo, ma più ne avremo più sarà difficile regolare l’effetto finale. Di solito, basta avere un paio di marker ad opacità zero sugli estremi per avere un effetto di fade sulla nascita e sull’eliminazione delle particelle, per evitare una brusca apparizione o scomparsa delle stesse (utile per sistemi particellari come il fumo).

Altri moduli sono by Speed: come dice il nome, influenzano i valori di una particella in base alla sua velocità, la quale può dipendere non solo da Start Speed ma anche da moduli come Velocity over Lifetime o Force over Lifetime, che influenzano la velocità delle singole parrticelle.

È facile esagerare attivando tutti i moduli… ma gli effetti cumulativi sono spesso difficilmente decifrabili. Il consiglio è attivare e regolare un modulo per volta, prima di passare all’effetto finale.

Altri moduli specifici: Wind Zones e collisioni

Il modulo External Forces, se attivo, dice al sistema particellare di farsi influenzare dalle Wind Zones. Le Wind Zones sono gameObject che rappresentano dei campi di forza di due tipi: direzionale (utile per simulare il vento) o sferico (utile per creare esplosioni). Una Wind Zone direzionale appare così in scena:

Page 137: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

136

Una volta create (GameObject > Create Other > Wind Zone), basta regolarle tramite i parametri di forza, turbolenza e impulso per ottenere il risultato ottimale, e poi abilitare sul sistema particellare il modulo External Forces e decidere quanto (Multiplier) le particelle vengono influenzate da tutte le Wind Zone.

Il modulo Collision permette di far urtare le particelle contro due tipi di oggetti: dei piani “virtuali”, o il mondo di gioco. Nel primo caso, Planes (molto più leggero dal punto di vista computazionale) si possono creare gameObject vuoti mediante il tasto + oppure assegnarne di esistenti, e Unity farà collidere le particelle contro dei piani allineati con questi gameObject e orientati verso la Y up di questi.

Nel secondo caso World (più pesante da calcolare) le particelle urteranno contro qualunque Collider incontreranno. Va da sé che se è possibile approssimare l’urto con un piano (come nel caso di un pavimento), è inutile utilizzare la modalità World, sprecando risorse. In

Page 138: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

137

entrambi i casi si potranno regolare le proprietà dell’urto mediante le differenti proprietà di questo modulo.

Il modulo Sub Emitters permette di creare altri sistemi particellari in risposta ad alcuni eventi, come la nascita di una particella, una collisione, o la sua morte. I sistemi da creare possono essere in scena, o dei prefab nel Project. Questo modulo permette di creare effetti molto elaborati (come particelle che sono pezzetti di vetro e che si rompono in tante piccole scheggie quando urtano), ma è ovvio che il numero di particelle potrebbe diventare facilmente spropositato… da usare con cautela, soprattutto su piattaforme mobile!

Ancora, Texture Sheet Animation permette di creare particelle “animate a mano”, usando la texture della particella come un foglio di animazione su cui sono disegnati i vari frame. Nel fare ciò, si può regolare la dimensione di questi frame con Tiles e la loro durata con Frame over Time e Cycles. In più, scegliendo il valore Single Row nel parametro Animation ed abilitando Random Row, è possibile avere un foglio di animazione con più file di frame, con leggere variazioni, per avere dei sistemi particellari molto randomici e naturali.

Renderizzare le particelle

Infine, il modulo Renderer (già attivo di default) definisce come le particelle vengono renderizzate. Come già detto, di default le particelle sono billboard – ovvero guardano verso la camera. Ecco perché il parametro di Start Rotation non è un quaternione, ma un singolo valore: la rotazione delle particelle si intende sull’asse che va dalla particella alla camera, ovvero il suo asse Y locale (per cui non potremo mai vedere il retro di una particella, perché non può ruotare sul suo asse X).

Come tipo quindi possiamo usare Billboard, o dei tipi particolari di Billboard: Stretched deforma la particella in base alla sua velocità (per creare un “effetto raggi”), mentre nel caso di Horizontal e Vertical le particelle vengono orientate non verso la camera ma verso (rispettivamente) l’alto, e il davanti. Ad esempio nell’immagine sotto, nel caso di Horizontal Billboard le particelle ad un certo punto si vedranno di taglio:

Page 139: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

138

In quest’altro esempio invece, usiamo la modalità Stretched Billboard:

Page 140: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

139

Le particelle assumono la forma di raggi. Il problema che nasce qui è che visto che sono stirate in entrambi i sensi, quando nascono dal punto d’origine sono già lunghe e creano quell’effetto “mazzo di fiori” che si vede in basso. Per rimediare, potremmo ricorrere a diversi metodi: farle nascere invisibili mediante Color over Lifetime, modificare l’emitter in modo che nascano da un punto più in alto, ecc.

Non resta che decidere che materiale usare per il sistema particellare tramite Material, e regolarlo mediante Cast Shadows e Receive Shadows.

In ultimo, Max Particle Size serve, in caso di particelle troppo grandi appartenenti ad un sistema particellare troppo vicino alla camera, ad impedire che queste coprano completamente lo schermo: ad esempio, il valore iniziale (0.5) indica che una particella non può mai essere più grande di metà dello schermo.

32.Gestire l’audio, soure e listener

Page 141: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

140

In questa lezione vedremo come aggiungere audio ad un gioco fatto in Unity, mediante una combinazione di componenti che si usano per riprodurre suoni, ed uno (di solito posto sulla telecamera) che si occupa di ascoltarli.

Importare suoni

Per prima cosa prima di poter mandare in play dei suoni, dobbiamo importare un file audio in un oggetto AudioClip (questo è anche il nome della classe suono, quando ci troveremo a programmare). Unity supporta diversi formati: non compressi come .wav o .aiff, oppure compressi come .mp3, e .ogg.

I suoni salvati in formati compressi occupano meno spazio su disco, perché sono stati

compressi, ovvio, e anche perché accettiamo una certa perdità di qualità (questo dipende da

quanto era distruttiva la compressione applicata!). Tuttavia essi vanno decompressi prima di

essere suonati. Per questo motivo è preferibile utilizzarli per suoni lunghi, come la musica, che

dopo la prima decompressione possono essere riprodotti in tranquillità per diversi minuti.

Viceversa, i file non compressi sono più adatti a suoni brevi, per i quali la compressione non

farebbe risparmiare molto spazio. Inoltre, il processore non deve fare lo sforzo di

decomprimerli prima di mandarli in play, il che li rende adatti ad un “uso frequente”, come nel

caso degli effetti sonori.

Indipendentemente dal formato di origine, in alcuni casi Unity permette di cambiare da compresso a non compresso e viceversa.

Importiamo un file .wav (semplicemente trascinandolo nella cartella del progetto) e selezioniamolo. Questo è quello che appare nell’Inspector:

Page 142: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

141

Come dicevamo, Audio Format ci permette di scegliere fra Native e Compressed. Scegliendo Compressed, abiliteremo l’opzione in basso Compression per decidere a che bitrate comprimere il suono. Come nel caso delle texture, ricordiamo che questa compressione non è distruttiva: Unity conserva in ogni caso una copia dell’asset originale, quindi possiamo sentirci liberi di sperimentare con diversi bitrate (il che vuol dire premere il tasto Apply, altrimenti le modifiche non vengono salvate).

Quando un suono è compresso, alla pressione del tasto Apply, Unity rivela anche la dimensione che questo ha sul disco una volta compresso, in questo caso 153 KB, degli originali 349 KB:

Page 143: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

142

3D Sound è un’opzione molto interessante, che rende il suono localizzabile nello spazio. Vale a dire che quando il suono viene mandato in play da un componente presente su un oggetto, esso verrà trattato come se fosse stato fisicamente emesso precisamente nelle coordinate di quell’oggetto, il che permette tutta una serie di possibilità: in primis, di localizzare il suono (meglio che in maniera stereo!), ridurne il volume in base alla distanza dall’ascoltatore, e di produrre l’effetto Doppler se l’ascoltatore o il suono si stanno muovendo l’uno rispetto all’altro.

Al contrario, un suono 2D (ovvero quando 3D è disattivo) è un semplice suono stereo o mono, che viene suonato in base al suo volume e al suo bilanciamento destra-sinistra, senza avere una collocazione nello spazio. è la scelta più classica per effetti sonori di menu o per la musica, che ha già un bilanciamento di suo dei vari strumenti (a meno che non sia musica proveniente da un oggetto contestualizzato in una scena 3D, come una radio o un grammofono).

Load Type indica il modo in cui il suono viene decompresso, e quando. Decompress on Load indica che il suono viene decompresso appena caricato, ovvero appena la scena viene lanciata, ancora prima di dover essere suonato. In questo modo il suono rimane in memoria occupando “molto” spazio, ma quando sarà necessario suonarlo sarà istantaneamente pronto.

Compressed in memory indica che il suono viene caricato, ma mantenuto in memoria in maniera compressa. Solo quando dovrà essere suonato verrà decompresso ‘al volo’, con una piccola perdita di performance. Se il suono è molto lungo, questa perdita potrebbe diventare un problema.

Infine, Stream from Disc indica che il suono non viene pre-caricato, ma letto da disco solo al momento in cui serve. Qui abbiamo il minore impatto sulla memoria in principio, e poi un po’ di lavoro da fare sul disco al momento in cui il suono deve essere suonato. è consigliabile non avere troppi suoni in streaming da disco allo stesso tempo, perché la lettura da disco (specialmente se un hard disk) potrebbe non essere così veloce da supportarli tutti insieme.

Hardware Decoding e Gapless Looping sono opzioni dedicate ai cellulari iOS e Android, utili per ottimizzare i suoni in maniera specifica su queste piattaforme.

Riprodurre i suoni (Play)

Una volta importato un suono, può essere mandato in play da un componente specifico che si chiama Audio Source. Tutti i suoni in Unity partono da un Audio Source, anche se più suoni potrebbero condividere lo stesso Audio Source.

Page 144: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

143

Ad esempio, un oggetto solo potrebbe occuparsi di lanciare la riproduzione di tutti i suoni dell’interfaccia di un menu. L’unico problema sarebbe che in quel caso non si potrebbero sovrapporre due suoni (perché l’Audio Source può suonare un suono alla volta).

Per ascoltare i suoni serve un altro componente, di nome Audio Listener. Questi agisce come un orecchio, o un microfono, ascoltando tutti gli Audio Source nelle vicinanze e nel raggio d’azione (definito dall’Audio Source, dal suo volume, ecc.). Di fatto, il componente Audio Listener non ha opzioni o impostazioni:

Di contro, l’Audio Source ha una marea di impostazioni che permettono di controllare la riproduzione dei suoni nel dettaglio. Creiamo un oggetto vuoto ed assegniamo un AudioSource:

La prima cosa da assegnare è l’Audio Clip, ovvero l’asset sonoro importato prima, altrimenti l’AudioSource non suonerà nulla. Volume, Loop e Pitch sono abbastanza autoesplicativi, ma andiamo a vedere altre proprietà peculiari.

Play On Awake deve essere impostato se vogliamo che l’effetto sonoro suoni appena l’oggetto appare in scena. Se così non fosse, dovremo usare lo scripting per mandare in

Page 145: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

144

play il suono in un secondo momento (così come per Mute, se fosse attivo dovremmo agire via programmazione per rimuoverlo e sentire il suono).

Bypass Effects, Bypass Listener Effects e Bypass Reverb Zones servono tutti e tre a rendere l’Audio Source temporaneamente “immune” ad effetti e zone di riverbero.

Priority indica la priorità che questo audio ha nell’essere suonato in caso di situazioni critiche. Vale a dire, ammettiamo che ci siano una miriade di suoni in play e che l’hardware non abbia le risorse per suonarli tutti, Priority indica le chance che questo suono venga suonato rispetto agli altri (maggiore la priorità, più risorse gli verranno dedicate).

Come detto prima, un suono può essere 3D, e sotto 3D Sound Settings possiamo regolare come si comporta rispetto al posizionamento.

Doppler Level indica quanto il suono rispetta l’effetto Doppler, dove 0 = nessun effetto e valori maggiori di 1 indicano un effetto accentuato.

Importantissimo è il Volume Rolloff: indica quanto il volume viene attenuato in base alla distanza fra l’Audio Source e l’Audio Listener. Logarithmic è un rolloff molto brutale, dove il volume decresce rapidamente. Linear è più graduale, il che vuol dire che il suono si sente anche a maggiori distanze.

Il rolloff può essere regolato mediante Min Distance e Max Distance: al di sotto di Min Distance, il suono è a pieno volume. Il volume decresce con la distanza (secondo il grafico in basso) fino a diventare zero in corrispondenza di Max Distance.

Nota: il fatto che Max Distance sia 500 (ad esempio) vuol dire che il suono teoricamente muore a 500 unità dall’emettitore, ma con il Logarithmic falloff già anche a 10 unità potrebbe non sentirsi granché. Come si può vedere dalla figura sopra, la linea verticale rossa nel grafico indica la posizione dell’Audio Listener. Anche se questi è distante circa 15 unità, il volume è già 0.05, quasi inaudibile. Se nel creare un suono non lo sentite, e questo suono è 3D, controllate per prima cosa se il problema è il falloff (basta impostare temporaneamente il suono come 2D per verificare se il problema è quello).

Zone di riverbero ed Effetti

Le zone di riverbero sono, come suggerisce il nome, delle aree nelle quali i suoni vengono distorti come a simulare il riverbero di ambienti chiusi. Si può creare una zona di riverbero andando su GameObject > Create > Reverb Zone. Questa verrà visualizzata come due sfere concentriche nella Scene View:

Page 146: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

145

Quando un Audio Listener si trova nella sfera più interna, l’effetto riverbero è al 100%. Va sfumando fino alla sfera esterna, dove diventa 0%. Mentre l’Audio Listener è all’interno della sfera più esterna, qualunque Audio Source subisce l’effetto riverbero (a meno che su di esso non sia impostato Bypass Reverb Zones).

Visto dall’Inspector, il componente Reverb Zone presenta poche impostazioni: la possibilità di definire l’area del riverbero, ed una serie di parametri (complessi!) per gestire qualunque aspetto del riverbero. Fortunatamente il valore Reverb Preset ci fornisce una nutrita lista di preset fra cui scegliere:

Oltre al riverbero, esistono tutta una serie di effetti audio che si possono aggiungere agli Audio Source, come eco, high pass, ecc. Sfortunatamente, questi sono solo per gli utenti di Unity Pro, quindi non ci soffermeremo molto su di essi. Basterà dire che, come tutti i componenti, si applicano dal menu Component, e possono essere spostati o rimossi dall’Inspector.

L’unica cosa degna di nota è che l’ordine in cui questi appaiono nell’Inspector è importante, perché vengono applicati dall’alto verso il basso (nell’immagine, prima il Low Pass e poi il Chorus):

Nota: I filtri audio sono una feature abbastanza pesante in termini computazionali, rendendone difficile l’uso su mobile.

Page 147: Guida a Unity 3D - wasdprogrammer.altervista.orgwasdprogrammer.altervista.org/.../2015/12/Guida-Unity-COMPLETA.pdf · Unity è disponibile dalla pagina di download sul sito di Unity

146