Anmerkung
Hallo an alle. Vor relativ kurzer Zeit schrieb ich einen Artikel über das Generieren einer Umgebung basierend auf Sound und Musik in Unity3D , in dem ich einige Beispiele für Spiele gab, die die Mechanik der Generierung von Inhalten basierend auf Musik verwenden, und sprach auch über die grundlegenden Aspekte solcher Spiele. Der Artikel enthielt praktisch keinen Code und ich versprach, dass es eine Fortsetzung geben würde. Und hier ist es vor dir. Dieses Mal werden wir versuchen, aus Ihrer Musik eine Strecke für ein 2D-Rennen im Stil von Hill Climb zu erstellen. Mal sehen, was wir bekommen ..

Einführung
Ich erinnere Sie daran, dass diese Artikelserie für Anfänger und diejenigen gedacht ist, die gerade erst angefangen haben, mit Sound zu arbeiten. Wenn Sie eine schnelle Fourier-Transformation in Ihrem Kopf durchführen, werden Sie sich wahrscheinlich langweilen.
Hier ist unsere Road Map für heute:
- Überlegen Sie, was Diskretisierung ist.
- Finden Sie heraus, welche Daten wir von Audio Clip Unity erhalten können
- Verstehen Sie, wie wir mit diesen Daten arbeiten können.
- Finden Sie heraus, was wir aus diesen Daten generieren können.
- Erfahren Sie, wie Sie aus all dem ein Spiel machen (na ja, oder etwas Ähnliches wie ein Spiel).
Also lass uns gehen!
Diskretisierung von analogem Singhalesisch
Wie viele Menschen wissen, müssen wir ein Signal konvertieren, um es in digitalen Systemen verwenden zu können. Einer der Konvertierungsschritte ist die Signalabtastung, bei der das analoge Signal in Teile unterteilt wird (temporäre Berichte), wonach jedem Bericht der Amplitudenwert zugewiesen wird, der zum ausgewählten Zeitpunkt war.

Der Buchstabe T gibt die Abtastperiode an. Je kürzer die Periode ist, desto genauer ist die Signalumwandlung. Meistens sprechen sie jedoch von der Umkehrung: Abtastrate (es ist logisch, dass dies F = 1 / T ist). 8.000 Hz reichen für einen Telefonsingal aus, und beispielsweise erfordert eine der Optionen für das DVD-Audio-Format eine Abtastfrequenz von 192.000 Hz. Der Standard für digitale Aufnahmen (in Spieleditoren, Musikeditoren) ist 44 100 Hz - dies ist die Frequenz von CD-Audio.
Die numerischen Werte der Amplitude werden in den sogenannten Samples gespeichert und mit ihnen werden wir arbeiten. Der Wert der Stichprobe ist float und kann zwischen -1 und 1 liegen. Vereinfacht sieht es so aus.

Schallwellenwiedergabe (statisch)
Die Wellenform (oder Audioform und bei gewöhnlichen Menschen - „Fisch“) ist eine visuelle Darstellung des Tonsignals über die Zeit. Die Wellenform kann uns zeigen, an welchem Punkt im Schall die aktive Phase auftritt und wo die Dämpfung auftritt. Oft wird die Wellenform für jeden Kanal separat dargestellt, zum Beispiel wie folgt:

Stellen Sie sich vor, wir haben bereits eine AudioSource und ein Skript, in dem wir arbeiten. Mal sehen, was uns die Einheit geben kann.
Wählen Sie Anzahl der Berichte
Bevor wir weiter gehen, müssen wir ein wenig über die Rendering-Tiefe unseres Sounds sprechen. Mit einer Abtastfrequenz von 44100 Hz pro Sekunde können wir 44100 Berichte verarbeiten. Angenommen, wir müssen einen 10 Sekunden langen Track rendern. Wir werden jeden Bericht mit einer Pixelbreite zeichnen. Es stellt sich heraus, dass unsere Wellenform 441.000 Pixel lang sein wird. Sie erhalten eine sehr lange, langgestreckte und wenig verstandene Schallwelle. Aber darin können Sie jeden spezifischen Bericht sehen! Und Sie werden das System schrecklich laden, egal wie Sie es zeichnen.

Wenn Sie keine professionelle Audiosoftware erstellen, benötigen Sie keine solche Genauigkeit. Für ein allgemeines Audiobild können wir alle Samples in größere Zeiträume aufteilen und beispielsweise den Durchschnitt von jeweils 100 Samples nehmen. Dann wird unsere Welle eine ganz bestimmte Form haben:

Dies ist natürlich nicht ganz korrekt, da Sie die möglicherweise benötigten Lautstärkespitzen überspringen können, sodass Sie nicht den Durchschnittswert, sondern das Maximum aus diesem Segment ausprobieren können. Dies ergibt ein etwas anderes Bild, aber Ihre Spitzen verschwinden nicht.
Vorbereiten des Audioempfangs
Definieren wir die Genauigkeit unserer Stichprobe als Qualität und die endgültige Anzahl der Berichte als sampleCount.
int quality = 100; int sampleCount = 0; sampleCount = freq / quality;
Ein Beispiel für die Berechnung aller Zahlen finden Sie unten.
Als nächstes müssen wir die Proben selbst bekommen. Dies kann aus einem Audioclip mit der GetData- Methode erfolgen.
public bool GetData(float[] data, int offsetSamples);
Diese Methode verwendet ein Array, in das Samples geschrieben werden. offsetSamples - Parameter, der für den Startpunkt des Lesens des Datenarrays verantwortlich ist. Wenn Sie das Array von Anfang an lesen, sollte es Null geben.
Um Samples aufzunehmen, müssen wir ein Array für sie vorbereiten. Zum Beispiel so:
float[] samples; float[] waveFormArray;
Warum haben wir die Länge mit der Anzahl der Kanäle multipliziert? Jetzt werde ich erzählen ...
Viele Leute wissen, dass wir im Sound normalerweise zwei Kanäle verwenden: links und rechts. Jemand weiß, dass es 2.1-Systeme sowie 5.1, 7.1 gibt, in denen Schallquellen von allen Seiten umgeben sind. Das Thema der Kanäle ist im Wiki gut beschrieben. Wie funktioniert das in Unity?
Beim Herunterladen einer Datei und beim Öffnen eines Clips finden Sie das folgende Bild:

Hier wird nur gezeigt, dass wir zwei Kanäle haben, und Sie können sogar feststellen, dass sie sich voneinander unterscheiden. Unity zeichnet nacheinander Samples dieser Kanäle auf. Es stellt sich dieses Bild heraus:
Deshalb benötigen wir doppelt so viel Platz im Array als nur für die Anzahl der Samples.
Wenn Sie die Option Force To Mono-Clip auswählen, ist der Kanal einer und der gesamte Ton befindet sich in der Mitte. Die Vorschau Ihrer Welle ändert sich sofort.


Audio-Daten empfangen
Folgendes bekommen wir:
private int quality = 100; private int sampleCount = 0; private float[] waveFormArray; private float[] samples; private AudioSource myAudio; void Start() { myAudio = gameObject.GetComponent<AudioSource>(); int freq = myAudio.clip.frequency; sampleCount = freq / quality; samples = new float[myAudio.clip.samples * myAudio.clip.channels]; myAudio.clip.GetData(samples,0);
Insgesamt, wenn der Track 10 Sekunden lang ist und zweikanalig ist, erhalten wir Folgendes:
- Die Anzahl der Samples im Clip (myAudio.clip.sample) = 44100 * 10 = 441000
- Das Samples-Array für zwei Kanäle ist lang (Samples.Length) = 441000 * 2 = 882000
- Anzahl der Berichte (sampleCount) = 44100/100 = 441
- Die Länge des endgültigen Arrays = samples.Length / sampleCount = 2000
Infolgedessen werden wir mit 2000 Punkten arbeiten, was ausreicht, um die Welle zu zeichnen. Jetzt müssen Sie die Vorstellungskraft einbeziehen und darüber nachdenken, wie wir diese Daten verwenden können.
Erstellen Sie mit den Debug-Tools eine einfache Audiospur
Wie viele wissen, verfügt Unity über bequeme Mittel zum Anzeigen aller Arten von Debug-Informationen. Ein intelligenter Entwickler, der auf diesen Tools basiert, kann beispielsweise sehr leistungsfähige Erweiterungen für den Editor vornehmen. Unser Fall zeigt eine sehr untypische Verwendung von Debug-Methoden.
Zum Zeichnen brauchen wir eine Linie. Wir können dies mit Hilfe eines Vektors tun, der aus den Werten unseres Arrays erstellt wird. Bitte beachten Sie, dass wir die beiden Hälften unserer Visualisierung „kleben“ müssen, um eine schöne Spiegel-Audio-Form zu erstellen.
for (int i = 0; i < waveFormArray.Length - 1; i++) {
Verwenden Sie als Nächstes einfach Debug.DrawLine, um unsere Vektoren zu zeichnen. Jede Farbe kann wählen. Alle diese Methoden müssen in Update aufgerufen werden, damit wir die Informationen in jedem Frame aktualisieren.
Debug.DrawLine(upLine, downLine, Color.green);
Wenn Sie möchten, können Sie einen „Schieberegler“ hinzufügen, der die aktuelle Position des abgespielten Titels anzeigt. Diese Informationen erhalten Sie im Feld "AudioSource.timeSamples".
private float debugLineWidth = 5;
Insgesamt ist hier unser Skript:
using UnityEngine; public class WaveFormDebug : MonoBehaviour { private readonly int quality = 100; private int sampleCount = 0; private int freq; private readonly float debugLineWidth = 5; private float[] waveFormArray; private float[] samples; private AudioSource myAudio; private void Start() { myAudio = gameObject.GetComponent<AudioSource>();
Und hier ist das Ergebnis:

Erstellen Sie mit PolygonCollider2D eine glatte Klanglandschaft
Bevor ich mit diesem Abschnitt fortfahre, möchte ich Folgendes beachten: Natürlich macht es Spaß, auf dem aus Musik erzeugten Track zu fahren, aber aus Sicht des Gameplays ist es praktisch nutzlos. Und hier ist warum:
- Damit der Track passierbar ist, müssen wir unsere Daten glätten. Alle Spitzen verschwinden und Sie hören praktisch auf, "Ihre Musik zu fühlen"
- Normalerweise sind Musiktitel stark komprimiert und stellen einen Soundstein dar, der für ein 2D-Spiel schlecht geeignet ist.
- Das ungelöste Problem der Geschwindigkeit unseres Transports, das für die Geschwindigkeit der Strecke geeignet sein sollte. Ich möchte dieses Problem im nächsten Artikel behandeln.
Daher ist diese Art der Generierung als Experiment ziemlich lustig, aber es ist schwierig, ein echtes Gameplay-Feature darauf basierend zu erstellen. In jedem Fall fahren wir fort.
Wir müssen also PolygonCollider2D unter Verwendung unserer Daten erstellen. Das ist einfach zu machen. PolygonCollider2D verfügt über ein öffentliches Punktefeld, das Vector2 [] akzeptiert. Zuerst müssen wir unsere Punkte auf die Vektoren des gewünschten Typs übertragen. Lassen Sie uns eine Funktion erstellen, um das Array unserer Samples in ein Vektorarray zu übersetzen:
private Vector2[] CreatePath(float[] src) { Vector2[] result = new Vector2[src.Length]; for (int i = 0; i < size; i++) { result[i] = new Vector2(i * 0.01f, Mathf.Abs(src[i] * lineScale)); } return result; }
Übergeben Sie danach einfach unser resultierendes Vektorarray an den Collider:
path = CreatePath(waveFormArray); poly.points = path;
Wir schauen uns das Ergebnis an. Hier ist der Anfang unseres Tracks ... hmm ... er sieht nicht sehr passabel aus (denken Sie noch nicht an die Visualisierung, Kommentare werden später kommen).

Wir haben eine zu scharfe Audioform, daher kommt der Track komisch heraus. Müssen es glätten. Hier verwenden wir den gleitenden Durchschnittsalgorithmus. Weitere Informationen zu Habr finden Sie im Artikel Der Algorithmus für den gleitenden Durchschnitt (Simple Moving Average) .
In Unity wird der Algorithmus wie folgt implementiert:
private float[] MovingAverage(int frameSize, float[] data) { float sum = 0; float[] avgPoints = new float[data.Length - frameSize + 1]; for (int counter = 0; counter <= data.Length - frameSize; counter++) { int innerLoopCounter = 0; int index = counter; while (innerLoopCounter < frameSize) { sum = sum + data[index]; innerLoopCounter += 1; index += 1; } avgPoints[counter] = sum / frameSize; sum = 0; } return avgPoints; }
Wir ändern unsere Pfaderstellung:
float[] avgArray = MovingAverage(frameSize, waveFormArray); path = CreatePath(avgArray); poly.points = path;
Überprüfen ...

Jetzt sieht unsere Strecke ganz normal aus. Ich habe eine Fensterbreite von 10 verwendet. Sie können diesen Parameter ändern, um die gewünschte Glättung auszuwählen.
Hier ist das vollständige Skript für diesen Abschnitt:
using UnityEngine; public class WaveFormTest : MonoBehaviour { private const int frameSize = 10; public int size = 2048; public PolygonCollider2D poly; private readonly int lineScale = 5; private readonly int quality = 100; private int sampleCount = 0; private float[] waveFormArray; private float[] samples; private Vector2[] path; private AudioSource myAudio; private void Start() { myAudio = gameObject.GetComponent<AudioSource>(); int freq = myAudio.clip.frequency; sampleCount = freq / quality; samples = new float[myAudio.clip.samples * myAudio.clip.channels]; myAudio.clip.GetData(samples, 0); waveFormArray = new float[(samples.Length / sampleCount)]; for (int i = 0; i < waveFormArray.Length; i++) { waveFormArray[i] = 0; for (int j = 0; j < sampleCount; j++) { waveFormArray[i] += Mathf.Abs(samples[(i * sampleCount) + j]); } waveFormArray[i] /= sampleCount * 2; }
Wie ich am Anfang des Abschnitts sagte, hören wir mit dieser Glättung auf, den Track zu fühlen, außerdem ist die Geschwindigkeit der Maschine nicht an die Geschwindigkeit der Musik (BPM) gebunden. Wir werden dieses Problem im nächsten Teil dieser Artikelserie analysieren. Außerdem werden wir dort auf das Thema Specials eingehen. Effekte unter dem Takt. Übrigens habe ich eine Schreibmaschine aus diesem kostenlosen Gut genommen .
Wahrscheinlich haben sich viele von Ihnen beim Betrachten der Screenshots gefragt, wie ich den Track selbst gezeichnet habe. Collider sind schließlich nicht sichtbar.
Ich habe die Weisheit des Internets genutzt und einen Weg gefunden, wie Sie einen Polygon-Collider in ein Netz verwandeln können, dem Sie jedes Material zuweisen können, und der Linienrenderer einen stilvollen Umriss erstellt. Diese Methode wird hier ausführlich beschrieben. Triangulator können Sie gegen Unity Community übernehmen .
Fertigstellung
Was wir in diesem Artikel gelernt haben, ist eine grundlegende Skizze für Musikspiele. Ja, in dieser Form ist es bisher ein wenig hässlich, aber Sie können mit Sicherheit sagen "Leute, ich habe die Maschine auf der Audiospur laufen lassen!". Um dies zu einem echten Spiel zu machen, müssen Sie sich viel Mühe geben. Hier ist eine Liste, was wir hier tun können:
- Binden Sie die Geschwindigkeit der Maschine an die BPM-Spur. Der Spieler kann nur die Neigung des Autos steuern, nicht aber die Geschwindigkeit. Dann wird die Musik während des Kurses viel stärker empfunden.
- Machen Sie einen kleinen Detektor und fügen Sie Specials hinzu. Effekte, die unter dem Takt funktionieren. Darüber hinaus können Sie der Karosserie des Autos eine Animation hinzufügen, die im Takt eines Schlags abprallt. Es hängt alles von Ihrer Vorstellungskraft ab.
- Anstatt den gleitenden Durchschnitt zu bestimmen, müssen Sie die Spur kompetenter verarbeiten und ein Array von Daten abrufen, damit die Spitzen nicht verschwinden, aber es war einfach, eine Spur zu erstellen.
- Nun, und natürlich müssen Sie das Gameplay interessant machen. Sie können bei jedem Treffer ein Münzstück platzieren, Gefahrenzonen hinzufügen usw.
All dies und noch viel mehr werden wir in den verbleibenden Teilen dieser Artikelserie untersuchen. Vielen Dank fürs Lesen!