Una domanda interessante che sicuramente incontriamo lavorando con FMOD è la seguente: come faccio a sincronizzare alcuni elementi di gioco con la musica di sottofondo? Possiamo ottenere informazioni sulle battute di un evento in Unity? La documentation di FMOD fornisce un esempio utile. In questo tutorial modificheremo leggermente l’esempio per renderlo utilizzabile in qualsiasi situazione di gioco.
Scarica il progetto Unity & FMOD per questo tutorial.
In primo luogo, ci serve un evento FMOD che abbia informazioni sui BPM e sulle battute. Queste informazioni possono essere inserite all’interno di un evento con il tasto destro del mouse sulla timeline sotto l’opzione Add Tempo Marker:
Opzionalmente possiamo anche inserire i Destinazione Marker. Questi si rivelano particolarmente utili se si desidera sincronizzare o controllare eventi nel gioco attraverso una determinata posizione in una traccia musicale o di un sound effect.
Creiamo uno script e gli diamo il nome BeatSystem. Aggiungiamo le seguenti righe di codice:
using System;
using System.Runtime.InteropServices;
using UnityEngine;
class BeatSystem : MonoBehaviour
{
[StructLayout(LayoutKind.Sequential)]
class TimelineInfo
{
public int currentMusicBeat = 0;
public FMOD.StringWrapper lastMarker = new FMOD.StringWrapper();
}
TimelineInfo timelineInfo;
GCHandle timelineHandle;
FMOD.Studio.EVENT_CALLBACK beatCallback;
public static int beat;
public static string marker;
public void AssignBeatEvent(FMOD.Studio.EventInstance instance)
{
timelineInfo = new TimelineInfo();
timelineHandle = GCHandle.Alloc(timelineInfo, GCHandleType.Pinned);
beatCallback = new FMOD.Studio.EVENT_CALLBACK(BeatEventCallback);
instance.setUserData(GCHandle.ToIntPtr(timelineHandle));
instance.setCallback(beatCallback, FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_BEAT | FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_MARKER);
}
public void StopAndClear(FMOD.Studio.EventInstance instance)
{
instance.setUserData(IntPtr.Zero);
instance.stop(FMOD.Studio.STOP_MODE.ALLOWFADEOUT);
instance.release();
timelineHandle.Free();
}
[AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
static FMOD.RESULT BeatEventCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, FMOD.Studio.EventInstance instance, IntPtr parameterPtr)
{
IntPtr timelineInfoPtr;
FMOD.RESULT result = instance.getUserData(out timelineInfoPtr);
if (result != FMOD.RESULT.OK)
{
Debug.LogError("Timeline Callback error: " + result);
}
else if (timelineInfoPtr != IntPtr.Zero)
{
GCHandle timelineHandle = GCHandle.FromIntPtr(timelineInfoPtr);
TimelineInfo timelineInfo = (TimelineInfo)timelineHandle.Target;
switch (type)
{
case FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_BEAT:
{
var parameter = (FMOD.Studio.TIMELINE_BEAT_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.TIMELINE_BEAT_PROPERTIES));
timelineInfo.currentMusicBeat = parameter.beat;
beat = timelineInfo.currentMusicBeat;
}
break;
case FMOD.Studio.EVENT_CALLBACK_TYPE.TIMELINE_MARKER:
{
var parameter = (FMOD.Studio.TIMELINE_MARKER_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.TIMELINE_MARKER_PROPERTIES));
timelineInfo.lastMarker = parameter.name;
marker = timelineInfo.lastMarker;
}
break;
}
}
return FMOD.RESULT.OK;
}
}
Il metodo AssignBeatEvent()
ci permette di assegnare un’istanza che è stata effettivamente creata e avviata da un altra parte a questo sistema di beat e marker- Se vogliamo fermare l’istanza, chiamiamo semplicemente StopAndClear()
. Nell’esempio FMOD, l’istanza è stata creata, avviata e assegnata al sistema nello stesso script , il che può creare confusione. Dopo tutto, vogliamo decidere noi quando e dove riprodurre i nostri suoni.
Creiamo un secondo script per testare questo sistema di beat e marker:
public class Musica : MonoBehaviour
{
private FMOD.Studio.EventInstance instance;
private BeatSystem bS;
void Start()
{
bS = GetComponent<BeatSystem>();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
instance = FMODUnity.RuntimeManager.CreateInstance("event:/musica");
instance.start();
bS.AssignBeatEvent(instance);
}
if (Input.GetKeyDown(KeyCode.LeftControl))
{
bS.StopAndClear(instance);
}
}
}
All’inizio dichiariamo l’istanza musicale e il nostro BeatSystem. Nella funzione Start() usiamo GetComponent()
per accedere al BeatSystem. Nella funziona Update() ripetiamo la stessa procedura usata negli altri tutorial per testare la funzionalità dello script: creiamo e avviamo l’istanza premendo la barra spaziatrice e con con il metodo bS.AssignBeatEvent(instance)
passiamo l’istanza al BeatSystem. Da questo momento in poi possiamo ottenere il valore dei battiti e le informazioni sul nome dei marker tramite BeatSystem.beat
e BeatSystem.marker
. Se premiamo il tasto di controllo sinistro, l’istanza viene fermata correttamente con il BeatSystem.
Nel progetto allegato ho creato una canvas con due elementi di testo per visualizzare le informazioni sull’evento riprodotto: