Quando si implementano i passi in Unity, ci sono diverse tecniche che possiamo utilizzare a seconda del genere di gioco (2D, FPS, ecc.). Alcuni giochi fanno uso di Animation Clips per animare il movimento del giocatore, altri muovono il giocatore solamente attraverso la fisica. Ci chiediamo se sia il caso di utilizzare dei loop che iniziano e si fermano metre “camminiamo”, o se si debbano utilizzare campioni individuali per ogni passo. Teoricamente, entrambi i metodi sono possibili e dipende davvero da come è stato programmato il gioco.
In questo tutorial vi mostrerò come implementare dei passi di un player 2D con e senza Animation Events di Unity.
Scarica il progetto Unity & FMOD su questo tutorial.
Implementari i passi attraverso Animation Events in Unity
Se il gioco fa uso di animazione sotto forma di Animation Clips, allora possiamo usare gli eventi di animazione per implementare i nostri suoni. Questi sono molto utili quando si tratta di sincronizzare le animazioni con l’audio.
Come funzioni gli Animation Events?
Gli eventi di animazione ci permettono di chiamare qualsiasi metodo su un keyframe specifico . Lo script con il metodo deve essere posizionato nello stesso GameObject che contiene il component Animator.
Preparazioni in FMOD Studio
Abbiamo bisogno di un evento che possiede una timelinee vuota e un parametro di gioco di qualsiasi nome, nel nostro caso “Terrain”. Dopo tutto, vogliamo riprodurre suoni diversi per i diversi tipi di pavimento:
Il parametro Terrain contiene 4 voci, in questo caso Grass, Gravel, Wood e Water. Poniamo in ciascuna di queste voci un Multi Instrument che contiene campioni individuali per il tipo di suolo. Se si desidera un solo tipo di terreno, è possibile omettere completamente il parametro Terrain e spostare il Multi Instrument sulla timeline.
Preparazione in Unity
In Unity dobbiamo definire dei layer e assegnarli ai nostri GameObjects che compongono il terreno. Andiamo su Edit -> Project Settings -> Tags & Layers e creiamo nuovi layer:
Water è già uno layer di default. Usiamo semplicemente questo layer per i nostri scopi. Poi passiamo attraverso i GameObject di terreno e selezioniamo il layer da noi preferito nell’angolo in alto a destra dell’inspector:
Script C# per i footsteps
Dopo questi preparativi selezioniamo il GameObject in cui si trova il component Animator responsabile delle animazioni del giocatore. Lì creiamo uno script per i passi. In primo luogo dichiariamo un enumeratore per i diversi tipi di pavimento/terrain e, naturalmente, l’istanza dell’evento FMOD:
private enum CURRENT_TERRAIN { GRASS, GRAVEL, WOOD_FLOOR, WATER };
[SerializeField]
private CURRENT_TERRAIN currentTerrain;
private FMOD.Studio.EventInstance foosteps;
Creiamo quindi il metodo DetermineTerrain()
che ci aiuterà a determinare il corretto tipo di terreno su cui si trova attualmente il giocatore:
private void DetermineTerrain()
{
RaycastHit[] hit;
hit = Physics.RaycastAll(transform.position, Vector3.down, 10.0f);
foreach (RaycastHit rayhit in hit)
{
if (rayhit.transform.gameObject.layer == LayerMask.NameToLayer("Gravel"))
{
currentTerrain = CURRENT_TERRAIN.GRAVEL;
break;
}
else if (rayhit.transform.gameObject.layer == LayerMask.NameToLayer("Wood"))
{
currentTerrain = CURRENT_TERRAIN.WOOD_FLOOR;
break;
}
else if (rayhit.transform.gameObject.layer == LayerMask.NameToLayer("Grass"))
{
currentTerrain = CURRENT_TERRAIN.GRASS;
}
else if (rayhit.transform.gameObject.layer == LayerMask.NameToLayer("Water"))
{
currentTerrain = CURRENT_TERRAIN.WATER;
}
}
}
Fondamentalmente inviamo un raggio dalla posizione del giocatore in direzione Vector3.down con una distanza di 10 unità di gioco. Nelle istruzioni if controlliamo se il raggio colpisce un GameObject con il rispettivo layer. Se è così, attualizziamo currentTerrain
. Inseriamo questo metodo nella funzione Update() di Unity.
Ora ci occupiamo di riprodurre i suoni dei passi. Creiamo un metodo PlayFootsteps() semplice:
private void PlayFootstep(int terrain)
{
foosteps = FMODUnity.RuntimeManager.CreateInstance("event:/Footsteps");
foosteps.setParameterByName("Terrain", terrain);
foosteps.set3DAttributes(FMODUnity.RuntimeUtils.To3DAttributes(gameObject));
foosteps.start();
foosteps.release();
}
Il metodo riceve un argomento int che viene utilizzato per fissare il parametro di gioco “Terrain”. Definiamo questa variabile in un altro metodo:
public void SelectAndPlayFootstep()
{
switch (currentTerrain)
{
case CURRENT_TERRAIN.GRAVEL:
PlayFootstep(1);
break;
case CURRENT_TERRAIN.GRASS:
PlayFootstep(0);
break;
case CURRENT_TERRAIN.WOOD_FLOOR:
PlayFootstep(2);
break;
case CURRENT_TERRAIN.WATER:
PlayFootstep(3);
break;
default:
PlayFootstep(0);
break;
}
}
Attraverso questa istruzione switch riusciamo a riprodurre un suono di passo. Ma come facciamo a sapere se i valori int corrispondono a certi tipi di pavimento/terrain? Questi valori sono visualizzati a destra del label nella configurazione dei parametri di gioco in FMOD Studio:
Lo script completo per i footsteps è il seguente:
public class PlayerFootsteps : MonoBehaviour {
private enum CURRENT_TERRAIN { GRASS, GRAVEL, WOOD_FLOOR, WATER };
[SerializeField]
private CURRENT_TERRAIN currentTerrain;
private FMOD.Studio.EventInstance foosteps;
private void Update()
{
DetermineTerrain();
}
private void DetermineTerrain()
{
RaycastHit[] hit;
hit = Physics.RaycastAll(transform.position, Vector3.down, 10.0f);
foreach (RaycastHit rayhit in hit)
{
if (rayhit.transform.gameObject.layer == LayerMask.NameToLayer("Gravel"))
{
currentTerrain = CURRENT_TERRAIN.GRAVEL;
break;
}
else if (rayhit.transform.gameObject.layer == LayerMask.NameToLayer("Wood"))
{
currentTerrain = CURRENT_TERRAIN.WOOD_FLOOR;
break;
}
else if (rayhit.transform.gameObject.layer == LayerMask.NameToLayer("Grass"))
{
currentTerrain = CURRENT_TERRAIN.GRASS;
}
else if (rayhit.transform.gameObject.layer == LayerMask.NameToLayer("Water"))
{
currentTerrain = CURRENT_TERRAIN.WATER;
}
}
}
public void SelectAndPlayFootstep()
{
switch (currentTerrain)
{
case CURRENT_TERRAIN.GRAVEL:
PlayFootstep(1);
break;
case CURRENT_TERRAIN.GRASS:
PlayFootstep(0);
break;
case CURRENT_TERRAIN.WOOD_FLOOR:
PlayFootstep(2);
break;
case CURRENT_TERRAIN.WATER:
PlayFootstep(3);
break;
default:
PlayFootstep(0);
break;
}
}
private void PlayFootstep(int terrain)
{
foosteps = FMODUnity.RuntimeManager.CreateInstance("event:/Footsteps");
foosteps.setParameterByName("Terrain", terrain);
foosteps.set3DAttributes(FMODUnity.RuntimeUtils.To3DAttributes(gameObject));
foosteps.start();
foosteps.release();
}
}
Aggiungere i passi direttamente ai clip di animazione
Ora apri la finestra di animazione, clicca sul GameObject del giocatore (o su un Child-GameObject che contiene il componente Animator), poi seleziona una delle animazioni dall’elenco dropdown dei Animation Clip.
Clicchiamo con il tasto destro del mouse sotto la timeline e sopra i keyframe sullo spazio grigio scuro libero. Seleziona l’opzione Create Animation Event. Come risultato, un piccolo pulsante o freccia (l’Animation Event) dovrebbe essere stato posizionato nella posizione del keyframe desiderato. Nell’ispector ora ci accorgiamo che è possibile scegliere tra diversi metodi da un elenco dropdown:
Qui selezioniamo il metodo SelectAndPlayFootstep()
e ripetiamo il processo un’altra volta per un altro keyframe. E’ meglio selezionare sempre i keyframe dove il giocatore mette il piede sul terreno per gli eventi di animazione. In alcune situazioni, dove questo può non essere molto chiaro, è sufficiente che i due eventi di animazione siano alla stessa distanza l’uno dall’altro. Nel mio caso ho impostato gli eventi in questo modo:
Ripeti questo processo per ulteriori clip di animazione. Se hai fatto tutto per bene, dovresti essere in grado di sentire i tuoi passi nel gioco.
Implementare footsteps senza Animation Clips
Possiamo anche implementare i footsteps senza clip di animazione per i movimenti (alcuni giochi ne farne a meno). In teoria, creiamo un timer che emette un suono di passo quando il giocatore si muove nel momento che scegliamo noi.
In pratica, dichiariamo il nostro PlayerController, creiamo una variabile float per il timer e una variabile float per il tempo di ripetizione dei passi:
private PlayerController playerController;
float timer = 0.0f;
[SerializeField]
float footstepSpeed = 0.3f;
Nel metodo Awake() di Unity assegniamo il component PlayerController al nostro tipo dichiarato.
private void Awake()
{
playerController = GetComponentInParent<PlayerController>();
}
Dopodiché verifichiamo nel metodo Update() se il giocatore si muove e si trova su un terreno. Aggiungiamo Time.deltaTime al timer. Allo stesso tempo verifichiamo se il timer ha raggiunto la nostra variabile fissa footstepSpeed, riproduciamo un suono di passo a piedi e azzeriamo il timer:
private void Update()
{
DetermineTerrain();
if (playerController.IsWalking && playerController.IsGrounded)
{
if (timer > footstepSpeed)
{
SelectAndPlayFootstep();
timer = 0.0f;
}
timer += Time.deltaTime;
}
}
Se vogliamo riprodurre i passi più velocemente, possiamo modificare o rendere più piccola la variabile footstepSpeed. Nel PlayerController le variabili booleane IsWalking e IsGrounded sono già state pre programmate. Chiedi al tuo programmatore se può aiutarti. Puoi inoltre utilizzare liberamente l’implementazione del PlayerController allegato.