Sviluppare con Unity
2D e 3D
First Person Controller e Audio
A cura di Federico Bachis, [Link]@[Link]
In questa lezione:
1. Classi, istanze e interfacce
2. Ereditarietà e polimorfismo
3. Software design: SOLID
4. L’Audio Source e l’Audio Listener
5. Creare un First Person Controller.
Le variabili
Una variabile è un contenitore di informazioni, è composta da 4 elementi:
● Modificatore di accesso: public o private, determina se è accessibile dall’esterno della classe in cui
risiede
● Tipo dell’informazione che contiene può essere un tipo base (int, float, char o bool) oppure una
classe (Vector3, GameObject, Transform etc)
● Nome: arbitrario, ma scelto in maniera da esplicitare il contenuto (counter, speed, movement)
● Valore: può essere un valore numerico, una stringa etc. o un riferimento a un’istanza di classe (es.
un transform, un rigidbody, un gameobject o altro), deve essere coerente con ilt tipo dichiarato.
Una variabile ha 2 fasi fondamentali: la dichiarazione e l’inizializzazione.
● La dichiarazione comprende il modificatore di accesso, il tipo e la scelta del nome:
public int counter; //Dichiara una variabile pubblica, di tipo intero, chiamata counter
● L’inizializzazione è l’assegnazione di un valore a una variabile che sia stata dichiarata:
counter=0; //Assegna il valore 0, alla variabile chiamata counter
● Potete fare la dichiarazione e l’inizializzazione in una sola riga di codice:
public int counter = 0; //Dichiara una variabile pubblica di tipo int, chiamata counter, con valore 0
Modificatori
Modificatori di Accesso:
Influenzano la visibilità delle variabili, delle classi e dei metodi
● public: La classe o il membro è accessibile da qualsiasi altra classe o codice.
● private: La classe o il membro è accessibile solo all'interno della propria classe.
● internal: valore di default, renderà la classe o il membro accessibile all'interno dello
stesso assembly. (di default unity usa un solo assembly per tutto il progetto)
……………………………………….
● protected: La classe o il membro è accessibile all'interno della propria classe e nelle
classi derivate.
● protected internal: La classe o il membro è accessibile sia all'interno dello stesso
assembly che nelle classi derivate, anche se si trovano in assembly diversi.
I Tipi
I tipi possono essere divisi in tipi di base e in tipi complessi
● Tipi base che ci interessano sono numeri decimali (float), numeri
interi (int), booleani (bool), caratteri (char) e gruppi di caratteri
(string).
Questi tipi rappresentano una singola informazione, di un singolo tipo.
● I tipi complessi invece sono in grado di rappresentare più
informazioni dello stesso tipo (Vector3 rappresenta 3 float), o di tipi
diversi (Transform rappresenta 2 Vector3 scala e posizione e un
Quaternion per la rotazione).
Tutte le classi/script che scriviamo sono tipi complessi!
Differenza tra classe e istanza
Una classe è un tipo complesso, è un modello in grado di organizzare dati (chiamati attributi o
proprietà) all’interno di variabili e comportamenti (chiamati metodi).
L’istanza è un oggetto di una certa classe, in cui le variabili assumono specifici valori.
Per ogni classe possono esistere molte istanze.
Possiamo salvare il riferimento di una istanza di classe su una variabile del tipo opportuno.
Per esempio se definiamo una classe Room, potremmo avere l’istanza kitchen, studio o office.
Ogni Room sarà caratterizzata da valori delle variabili diversi (roomName, areaSize,
doorsNumber, windowNumber) e da metodi comuni (GoInside(), Explore(), GoOut() ecc)
public Room currentRoom = office; //Assegna alla variabile chiamata currentRoom di tipo Room
il riferimento all’istanza office.
Le Interfacce
Le interfacce sono tipi che dichiarano le variabili e i metodi di una classe, cioè senza inizializzare le
variabili e definendo la sola firma dei metodi senza implementarli.
Per questa ragione vengono anche chiamate contratti.
Non è possibile creare l’istanza di un’interfaccia!
Una interfaccia deve essere implementata da una classe e serve a garantire che tutte le classi che la
implementano abbiano i metodi e le variabili dichiarate dall’interfaccia.
public interface IHealable() //Dichiarazione di un’interfaccia, come quella di una classe
{
public int hp; //Dichiarazione di una variabile, non può essere inizializzata
public void Heal(int healAmount); //Firma di un metodo senza implementazione
}
Possiamo dichiarare variabili usando le interfacce come tipo della variabile
Ereditarietà e interfacce, in breve
//Esempio di ereditarietà:
Una classe può ereditare metodi e variabili da un’altra classe ed estendere le class Cane : Animale
sue proprietà e il suo comportamento base, indichiamo che una classe eredita { … }
da un’altra con i due punti : /*La classe Cane eredita da Animale o estende la
classe Animale.
Potete sempre usare una classe derivata dove è richiesta una classe base La classe Animale è detta la classe base di Cane.
(Polimorfismo). La classe Cane è detta classe derivata o figlia.*/
Le interfacce in C# definiscono un contratto: specificano quali metodi e //Esempio di contratto
proprietà una classe deve implementare, senza fornire un'implementazione. class Enemy : IDamageable, IHealable
Una classe può implementare più interfacce, permettendo una maggiore
{ /*Deve implementare TakeDamage e Heal*/}
flessibilità e supporto al polimorfismo.
public interface IDamageable
● Ereditarietà: riuso di codice e specializzazione tramite una classe {
base. void TakeDamage(int damage);
● Interfacce: definizione di un contratto che le classi devono seguire, }
favorendo la flessibilità. public interface IHealable
Le classi possono ereditare i metodi e le variabili di una sola classe, ma {
possono implementare tutte le Interfacce che si vuole, per farlo si usano void Heal(int healAmount);
sempre i 2 punti e si separano le diverse interfacce con la virgola. }
SOLID
Insieme di cinque principi di progettazione Object Oriented per creare codice
mantenibile, flessibile e comprensibile.
1. Single Responsibility Principle: Un solo compito per componente
2. Open/Closed Principle: Classi estendibili, non modificabili
3. Liskov Substitution Principle: Sostituire una classe base con una derivata non deve
causare problemi. (non stravolgete le classi base)
4. Interface Segregation Principle: Meglio molte interfacce piccole che dover
implementare funzionalità non necessarie per un componente
5. Dependency Inversion Principle: Le classi dovrebbero dipendere dalle
astrazioni/interfacce e non dalle implementazioni concrete
Single Responsibility Principle:
Ogni classe dovrebbe avere una sola responsabilità.
In Unity, ciò significa che ogni componente o classe dovrebbe gestire un singolo aspetto del comportamento di un
oggetto.
Esempio:
Una classe PlayerMovement dovrebbe essere responsabile solo del movimento del giocatore.
Open/Closed Principle:
Le classi dovrebbero essere aperte all'estensione ma chiuse alla modifica.
Ciò significa che dovresti essere in grado di aggiungere nuove funzionalità estendendo le classi esistenti
senza modificare il loro codice.
Esempio: Invece di modificare la classe Player ogni volta che aggiungi un nuovo tipo di attacco, puoi
estendere la classe Weapon per aggiungere nuovi attacchi.
Liskov Substitution Principle:
Le classi derivate, devono poter essere usate al posto della loro classe base
senza generare problemi,
Interface Segregation Principle:
Le classi non dovrebbero essere costrette dalle interfacce a implementare metodi che
non utilizzano.
È meglio creare interfacce più piccole e specifiche piuttosto che una grande interfaccia
con molte funzioni.
Dependency Inversion Principle:
Le classi dovrebbero dipendere da astrazioni, non da implementazioni concrete.
Questo significa che invece di fare riferimento a classi specifiche all'interno di altre
classi, dovresti usare interfacce o classi astratte per ridurre il legame tra le classi.
Player dipende dalla classe astratta Weapon, qualsiasi classe che
estende Weapon, come Gun, Bow o Sword deve poter essere usate
al suo posto
Shortcut per sistemare l’indentazione:
Regole di indentazione: Ctrl+k+d tieni premuto ctrl e premi k e d in sequenza
Un codice per essere leggibile, deve essere indentato.
L’indentazione è la distanza che ogni riga di codice ha dal margine sinistro, e viene utilizzata per
organizzare visivamente i blocchi di codice.
Ogni volta che aprite una nuova parentesi graffa:
{
Andate a capo e spostatevi di una tabulazione a destra (con il tasto tab).
L'IDE Visual Studio applica l’indentazione automaticamente, quindi in genere non è
necessario premere tab manualmente.
}
Per ogni parentesi graffa aperta deve essercene una chiusa, questo serve a incapsulare il codice
e separare la logica dei metodi, dopo che chiudete la graffa, dovreste avere nuovamente il codice
con il rientro precedente all’apertura.
Usando opportunamente l’indentazione e gli strumenti dell’IDE, sarà facile individuare errori nella
gestione delle graffe.
Istruzioni e operatori booleani Link Wikipedia costrutti
● if(condition){task1}else{task2} //Esegue task1 se la condizione è vera, task2 se è falsa *if else
● var a = condition ? value1 : value2; //Assegna value1 se la condizione è vera, assegna value2 se è falsa *Operatore ternario
● while(condition){task} //Esegue task sino a quando condition è vera, potrebbe non eseguirlo *Ciclo while
● do{task}while(condition); //Esegue task almeno una volta e lo ripete finchè condition è true *Ciclo do while
● foreach(item in items){task} //Per ogni elemento item in una lista ripete task *Ciclo foreach
● for(int i=0; i<n; i++){task} //Svolge task n volte a ogni esecuzione incrementa i di 1 unita *Ciclo for
● for(;condition;var++){task} //Svolge task sinchè condition è vera a ogni esecuzione incrementa var *Ciclo for
● return; o return value; //Termina l'esecuzione di un metodo e restituisce un valore
condition è un booleano, ovvero vale True o False e può essere il risultato di un’operazione, oppure una variabile di tipo bool.
Le operazioni che restituiscono booleani si fanno con operatori di confronto e operatori logici
● (a > b), (a >= b) //a è Maggiore di b, a è Maggiore o uguale di b
● (a < b), (a <= b) //a è Minore di b, a è Minore o uguale di b
● (a == b), ([Link](objB);) //a è Uguale b, objA è lo stesso oggetto di objB
● (a != b), (;) //a è Diverso da b, objA non è lo stesso di objB
● (condition1 && condition2) //Vero se condition1 e condition2 sono vere entrambre, falso altrimenti
● (condition1 || condition2) //Vero se condition1 è vera oppure se condition2 è vera, falso se entrambe sono false
Operatori
Operatori di confronto
Operatori aritmetici
Operazione vera se: Simbolo
Operazione Simbolo Operatori logici
Uguale a ==
Addizione + Operazione Simbolo
Diverso da !=
Sottrazione - AND &&
Minore di <
Moltiplicazione * OR ||
Minore o uguale a <=
Divisione / NOT !
Maggiore di >
Modulo (resto) % Maggiore o uguale a >=
Audio Listener
Se il component Camera rappresenta gli occhi del giocatore, l’AudioListener allora
rappresenta le orecchie, solitamente ha senso averlo nella stessa posizione della
camera.
● Un solo audio Listener attivo per scena.
● La posizione dell’audio listener determina come vengono percepiti i suoni 3D
nella scena
● L’audio listener funziona in combinazione con gli AudioSource
Component: Audio Source
L’AudioSource è la sorgente di un suono:
● Audio Resource è il campo che ospita la
l’AudioClip da riprodurre, il file audio.
● Potete regolare il volume con l’apposito slider
● Il flag Play On Awake determina se l’audio va’
riprodotto immediatamente
● Il flag loop determina se l’audio deve essere
riprodotto in loop
● Lo slider spatial blend determina se il suono deve
essere 2D o 3D
Esercizio: Audio e FPS Controller
● Aprire la scene 3_Kitchen_Audio_Scene, fare play e commentare il
SimpleCameraController
● Modifichiamo il controller per muovere l’inquadratura con il mouse a destra e
sinistra
● Aggiungiamo la possibilità di muoverci a destra e sinistra
● Creare degli AudioSource 3D usando il valore SpatialBlend
● Inserire della musica in background
● Scrivere uno script che riproduca dei suoni a intervalli casuali di tempo
settabili dall’inspector.
● Scegliere casualmente la posizione del suono
● Scegliere casualmente il suono
Liste
È possibile creare un insieme di variabili dello stesso tipo, come liste o array.
Per creare una lista di AudioClip la dichiariamo come:
public List<AudioClip> audioClips;
Per convenzione useremo un nome al plurale.
List è la keyword che rende la variabile audioClips una lista.
All’interno delle parentesi angolari <TipoDellaLista> dovremo inserire il tipo di oggetti che
contiene la lista, nell’esempio AudioClip.
Gli elementi di una lista possono essere letti usando un indice che va da 0 a C-1, dove C è il
conto degli elementi della lista.
audioClips[2]; //È il terzo elemento della lista (si conta da 0)
Possiamo contare gli elementi di una lista usando il metodo:
[Link](); //Conta da quanti elementi è composta la lista in quel momento
Array
Per creare un Array di AudioClip lo dichiariamo come:
public AudioClip[] audioClips;
Per convenzione useremo un nome al plurale.
Le due parentesi quadre dopo il tipo, indicano la dichiarazione di un array!
Gli elementi di un Array possono essere letti usando un indice che va da 0 a L-1, dove L è la
lunghezza dell’array.
audioClips[2]; //È il terzo elemento dell’Array (si conta da 0)
Possiamo leggere la lunghezza di un array accedendo al valore della proprietà Length:
[Link]; //La lunghezza di un array non può essere modificata dopo
l’inizializzazione
Liste o Array?
Le liste possono contenere un numero di elementi variabile, modificabile durante
l’esecuzione del gioco.
Gli Array una volta inizializzati hanno una dimensione fissa.
Le liste in generale sono meno performanti degli array, ma sono più flessibili.
Sia liste che array possono essere lette attraverso l’indice e in entrambe l’indice
parte da 0 per il primo elemento.
Potete leggere tutti gli elementi di liste e array utilizza il for o il foreach:
for(int i = 0; i<[Link](); i++) //se usate l’array [Link]
{
[Link](audioClips[i].name);
}
NB. Sia le liste che gli array possono essere di qualsiasi tipo o classe
Errori comuni
null reference -> Non avete inizializzato una variabile, solitamente succede
quando quella variabile avreste dovuto inizializzarla da inspector
index out of range -> avete provato a leggere un elemento di una lista o di un
array usando un indice non valido (negativo, oppure maggiore della lunghezza
dell’array o del conto della lista)
La classe Time
[Link], è una variabile statica della classe Time, rappresenta il tempo di gioco trascorso dal momento in cui si
preme play, in forma di float.
Se volete controllare che un’azione non possa ripetersi prima di un certo ritardo:
float delay = 5f;
if([Link]>=nextAction)
{
nextAction = [Link] + delay;
//Fai l’azione
}else{
[Link](“Non puoi ancora fare questa azione”);
}
[Link], ci dice quanto tempo è passato tra il frame precedente e quello corrente, utile per normalizzare uno
spostamento, se un oggetto si muove di v=10m/s e deltaTime vale 2s, quell’oggetto si sarà mosso di 20 metri
Link spiegazione matematica
Ricordi tutti i termini usati?
● Modificatore di ● Polimorfismo ● Liste
accesso ● Ereditarietà ● Array
● Tipo ● Indentazione ● Indice
● Valore ● Operatori aritmetici ● Funzione Count() liste
● Riferimento ● Operatori di controllo ● Parametro .Length array
● Dichiarazione ● Operatori logici ● Iterazione/Iterare
● Inizializzazione ● Audio Listener ● Ciclo for
● Classe ● Audio Source ● Ciclo foreach
● Istanza ● Audio Clip
● Tipo complesso ● Audio Mixer
● oggetto ● [Link]
● Interfaccia ● [Link]
● Contratto ● delay
● Implementazione ● [Link](a,b)
● Chiamata