Bei der Verwendung von FMOD wird häufig die Frage gestellt: wie synchronisiere ich bestimmte Gameplay-Elemente mit der Hintergrundsmusik? Können wir Informationen über Takte und Schläge eines FMOD-Events in Unity abgreifen? In den FMOD Docs finden wir ein hilfreiches Beispiel zu. In diesem Tutorial verändern wir das Beispiel leicht, um dieses in jeder Spielsituation einsetzbar zu machen.
Lade dir hier das Unity & FMOD Projekt zu diesem Tutorial herunter.
Als erstes brauchen wir ein FMOD Event, das über BPM- und Taktinformationen verfügt. Die Informationen können wir mit der rechten Maustauste auf der Timeline unter Add Tempo Marker
einfügen.
Optional können wir mit der rechten Maustaste auch Destination Marker einfügen, die dann in Unity angezeigt werden. Das ist besonders nützlich, wenn bestimmte Musikstellen etwas im Spiel steuern sollen.
Nun erstellen wir einen Skript, der den Namen BeatSystem trägt. Wir fügen folgende Zeilen ein:
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;
}
}
Die Methode AssignBeatEvent()
ermöglicht uns eine Instanz, die eigentlich woanders erstellt und gestartet wurde, diesem Beatsystem zuzuweisen. Wenn wir die Instanz stoppen möchten, dann rufen wir einfach StopAndClear()
auf. Im FMOD-Beispiel wurde die Instanz im gleichen Beatsystem-Skript erstellt und gestartet, was zu Verwirrungen führen kann. Wir wollen schließlich selbst entscheiden, wann und wo wir unsere Sounds abspielen.
Um dieses Beat- und Markersystem zu testen, erstellen wir einen zweiten Skript:
public class Musik : 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:/musik");
instance.start();
bS.AssignBeatEvent(instance);
}
if (Input.GetKeyDown(KeyCode.LeftControl))
{
bS.StopAndClear(instance);
}
}
}
Wir deklarieren am Anfang die Musikinstanz und das Beatsystem. In der Start()-Methode nutzen wir GetComponent(), um auf das Beatsystem zugreifen zu können. In Unitys Update()-Methode spielen wir dasselbe Spiel wie in den anderen Tutorials: ein Druck auf die Leertaste erstellt und startet die Instanz, mit bS.AssignBeatEvent(instance)
geben wir die Instanz an das Beatsystem weiter. Wichtig dabei ist eigentlich nur, dass wir die Schlagzahl und die Markerinformation jetzt über BeatSystem.beat
und BeatSystem.marker
abgreifen können. Drucken wir die linke Control-Taste, wird die Instanz ordungsgemäß mit dem Beatsystem gestoppt.
Ich habe im beigelegten Projekt noch eine Canvas mit zwei Textelementen erstellt, um die Informationen zu visualisieren: