Bei der Implementierung von Fußschritten in Unity gibt es nach Spielgenre (2D, FPS usw.) verschiedene Techniken, die wir anwenden können. Je nach dem, ob das Spiel Animationen für das „Gehen“ des Spielers verwendet, oder der Spieler allein durch die Physik bewegt wird, können wir uns für die Eine oder Andere Methode entscheiden. Es stellt sich die Frage, ob wir Loops, die wir beim „Gehen“ starten und stoppen, oder lieber einzelne Samples für jeden Schritt verwenden. Thereotisch geht beides und es kommt wirklich drauf an, wie das Spiel programmiert wurde.
In diesem Tutorial stelle ich vor, wie man Fußschritte mit und ohne Animation Events implementiert.
Lade dir das Unity & FMOD Projekt zu diesem Tutorial herunter.
Inhaltsverzeichnis
Fußschritte mit Animation Events in Unity implementieren
Wenn das Spiel über Animation in Form von Animation Clips verfügt, dann können wir Animation Events zur Implementierung von Sounds verwenden. Diese sind ziemlich nützlich, wenn es darum geht, Animationen mit Audio zu syncen.
Wie funktionieren Animation Events?
Animation Events ermöglichen uns an einen bestimmten Keyframe eine beliebige Methode aufzurufen. Der Skript mit der Methode muss sich im selben GameObject befindet, das auch den Animator-Component enthält.
Vorbereitung in FMOD Studio
In FMOD Studio brauchen wir für die Fußschritte ein Event, das über eine leere Timeline und einen Game-Parameter mit beliebigem Namen, in unserem Fall „Terrain“ verfügt. Wir wollen schließlich für verschiedene Bodentypen verschiedene Sounds abspielen:
Der Labeled-Parameter Terrain verfügt über 4 Einträge, in diesem Fall Grass, Gravel, Wood und Water. Wir platzieren an diesen Einträgen jeweils ein Multi-Instrument, das einzelnde Samples für die Bodenart enthält. Falls es doch nur ein Bodentyp sein soll, können wir den Terrain-Parameter komplett weglassen und das Multi-Instrument auf die Timeline verschieben.
Vorbereitung in Unity
In Unity müssen wir zusätzliche Layers definieren und diese unseren Boden-GameObjects zuweisen. Wir gehen auf Edit -> Project Settings -> Tags & Layers und legen neue Layers an:
Water ist ja schon ein Builtin-Layer. Wir verwenden dieses Layer dann einfach für unsere Zwecke. Anschließend gehen wir die Boden-GameObjects durch und wählen oben rechts im Inspector das von uns gewünsche Layer aus:
Skript für Fußschritte
Nach den Vorbereitungen wählen wir das GameObject aus, in dem sich der Animator-Component, der für die Player-Animationen zuständig ist befindet. Dort erstellen wir einen Skript für die Fußschritte. Wir deklarieren zuerst einen Enumerator für die verschiedenen Bodentypen und natürlich die FMOD Eventinstanz:
private enum CURRENT_TERRAIN { GRASS, GRAVEL, WOOD_FLOOR, WATER };
[SerializeField]
private CURRENT_TERRAIN currentTerrain;
private FMOD.Studio.EventInstance foosteps;
Danach erstellen wir die DetermineTerrain()
-Methode, die uns dabei helfen wird, den korrekten Bodentyp festzustellen, auf dem sich der Spieler gerade befindet:
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;
}
}
}
Im Grunde schicken wir hier einen Ray von der Spielerposition in Richtung Vector3.down mit einer Entfernung von 10 Spieleinheiten. In den If-Anweisungen prüfen wir, ob der Ray einen GameObject mit dem jeweiligen Layer trifft. Wenn ja, ändern wir currentTerrain
. Wir packen diese Methode in Unitys Update()-Funktion.
Nun kümmern wir uns noch um das Abspielen der Fußschritte-Sounds. Wir erstellen eine ganz einfache PlayFootsteps()-Methode:
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();
}
Die Funktion nimmt ein int-Argument, das zur Festlegung des Game-Parameters „Terrain“ genutzt wird. Diese Variable legen wir in einer anderen Methode fest:
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;
}
}
Durch dieses Switch-Case spielen wir schließlich einen Fußschritte-Sound ab. Doch woher wissen wir, dass die int-Werte bestimmte Bodentypen entsprechen? In FMOD Studio werden diese Werte bei der Game-Parameter Konfiguration rechts neben dem Label angezeigt:
Der vollständige Skript für die Fußschritte sieht nun so aus:
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();
}
}
Fußschritte direkt in den Animation Clips hinzufügen
Öffnen wir nun das Animationsfenster, klicke auf das Player-GameObject (oder ein Child-GameObject, das den Animator-Component enthält) und wählen anschließend eine der Walk-Animation aus der Animationsclip-Dropdown-Liste aus.
Wir klicken mit der rechten Maustaste unterhalb der Zeitleiste und oberhalb der Keyframes auf den freien dunkelgrauen Platz. Wir wählen die Option Create Animation Event
aus. In folge dessen sollte ein kleiner Button (das Animation Event) an der gewünschten Stelle/Keyframe platziert worden sein. Im Inspector sehen wir nun, dass wir aus einer Dropdown-Liste zwischen verschiedenen Funktionen auswählen können:
Wir wählen dort die Methode SelectAndPlayFootstep()
aus und wiederholen den Vorgang noch ein Mal. Am besten wählen wir für die Animation Events immer die Keyframes aus, bei denen der Spieler den Fuß auf dem Boden platziert. In manchen Situationen, bei denen das vielleicht nicht ganz klar ist, reicht es auch, wenn die zwei Animation Events im gleichen Abstand voneinander liegen. Bei mir sieht es jetzt so aus:
Wiederhole diesen Vorgang für weitere Animationen-Clips. Wenn du alles richtig gemacht hast, solltest du deine Fußschritte im Spiel hören können.
Fußschritte ohne Animation Clips in Unity implementieren
Auch ohne Animation Clips für Bewegungen (manche FPS-Spiele verzichten z.B. drauf) können wir Fußschritte implementieren. In der Theorie erstellen wir ein Timer, der beim Bewegen des Spielers in von uns gewählten Zeitabschnitten ein Fußschrittsound abspielt.
In der Praxis erstellen deklarieren wir den PlayerController, erstellen eine Float-Variable für den Timer und eine Float-Variable für die Wiederholungsrate der Fußschritte:
private PlayerController playerController;
float timer = 0.0f;
[SerializeField]
float footstepSpeed = 0.3f;
In Unitys Awake()-Methode greifen wir auf den PlayerController zu:
private void Awake()
{
playerController = GetComponentInParent<PlayerController>();
}
Anschließend prüfen wir in der Update()-Methode, ob der Spieler sich grad bewegt und auf dem Boden liegt. Wir addieren den Timer mit Time.deltaTime. Gleichzeitig prüfen wir, ob der Timer unsere festegelegte footstepSpeed Variable erreicht hat, spielen einen Fußschrittsound ab und setzen den Timer zurück:
private void Update()
{
DetermineTerrain();
if (playerController.IsWalking && playerController.IsGrounded)
{
if (timer > footstepSpeed)
{
SelectAndPlayFootstep();
timer = 0.0f;
}
timer += Time.deltaTime;
}
}
Wenn wir die Fußschritte schneller abspielen wollen, dann können wir die footstepSpeed Variable ändern bzw. kleiner machen. Im PlayerController wurden die Bool-Variablen IsWalking und IsGrounded schon vorprogrammiert. Frag deinen Programmierer, ob er dir hierbei helfen kann. Du kannst gerne auch die Implementierung im Beispiel-PlayerController nutzen.