Generasi lingkungan berbasis suara dan musik di Unity3D. Bagian 2. Membuat trek 2D dari musik

Anotasi


Halo semuanya. Baru-baru ini, saya menulis sebuah artikel Menghasilkan lingkungan berdasarkan suara dan musik di Unity3D , di mana saya memberikan beberapa contoh permainan yang menggunakan mekanisme menghasilkan konten berdasarkan musik, dan juga berbicara tentang aspek-aspek dasar dari permainan tersebut. Praktis tidak ada kode dalam artikel itu dan saya berjanji akan ada sekuelnya. Dan ini dia, di depanmu. Kali ini kami akan mencoba membuat trek untuk balapan 2D, dengan gaya Hill Climb, dari musik Anda. Mari kita lihat apa yang kita dapatkan ..



Pendahuluan


Saya mengingatkan Anda bahwa seri artikel ini dirancang untuk pengembang pemula dan bagi mereka yang baru saja mulai bekerja dengan suara. Jika Anda melakukan transformasi Fourier cepat dalam pikiran Anda, maka Anda mungkin akan bosan.


Berikut adalah Peta Jalan kami untuk hari ini:


  1. Pertimbangkan diskritisasi itu.
  2. Cari tahu data apa yang bisa kita dapatkan dari Audio Clip Unity
  3. Pahami bagaimana kami dapat bekerja dengan data ini.
  4. Cari tahu apa yang bisa kita hasilkan dari data ini.
  5. Pelajari cara membuat game dari semua ini (baik, atau yang serupa dengan game)

Jadi ayo pergi!


Diskretisasi Analog Sinhala


Seperti yang diketahui banyak orang, untuk menggunakan sinyal dalam sistem digital, kita perlu mengubahnya. Salah satu langkah konversi adalah pengambilan sampel sinyal, di mana sinyal analog dibagi menjadi beberapa bagian (laporan sementara), setelah itu setiap laporan diberikan nilai amplitudo yang ada pada saat yang dipilih.


Huruf T menunjukkan periode pengambilan sampel. Semakin pendek periode, semakin akurat konversi sinyal. Tetapi paling sering mereka berbicara tentang kebalikannya: Tingkat sampel (logis bahwa ini adalah F = 1 / T). 8.000 Hz cukup untuk panggilan telepon, dan, misalnya, salah satu opsi untuk format DVD-Audio memerlukan frekuensi sampling 192.000 Hz. Standar dalam perekaman digital (dalam editor game, editor musik) adalah 44 100 Hz - ini adalah frekuensi CD Audio.


Nilai numerik dari amplitudo disimpan dalam apa yang disebut sampel dan bersama mereka kita akan bekerja. Nilai sampel mengambang dan dapat dari -1 hingga 1. Sederhana, terlihat seperti ini.



Render gelombang suara (statis)


Informasi dasar


Bentuk gelombang (atau bentuk audio, dan pada umumnya orang - "ikan") adalah representasi visual dari sinyal suara dari waktu ke waktu. Bentuk gelombang dapat menunjukkan kepada kita pada titik apa dalam suara fase aktif terjadi, dan di mana redaman terjadi. Seringkali bentuk gelombang disajikan untuk setiap saluran secara terpisah, misalnya, seperti ini:



Bayangkan kita sudah memiliki AudioSource dan skrip tempat kita bekerja. Mari kita lihat apa yang bisa diberikan Unity kepada kita.


//  AudioSource    AudioSource myAudio = gameObject.GetComponent<AudioSource>(); //     .     44100. int freq = myAudio.clip.frequency; 

Pilih Jumlah Laporan


Sebelum kita melangkah lebih jauh, kita perlu berbicara sedikit tentang kedalaman render suara kita. Dengan frekuensi pengambilan sampel 44100 Hz per detik, kami dapat memproses 44100 laporan. Katakanlah kita perlu membuat trek berdurasi 10 detik. Kami akan menggambar setiap laporan dengan garis dalam lebar piksel. Ternyata bentuk gelombang kami akan mencapai 441.000 piksel. Anda mendapatkan gelombang suara yang sangat panjang, memanjang dan sedikit dipahami. Tetapi, di dalamnya Anda dapat melihat setiap laporan spesifik! Dan Anda akan sangat memuat sistem, tidak peduli bagaimana Anda menggambarnya.



Jika Anda tidak membuat perangkat lunak audio profesional, Anda tidak perlu keakuratan tersebut. Untuk gambar audio umum, kami dapat memecah semua sampel menjadi periode yang lebih besar dan mengambil, misalnya, rata-rata masing-masing 100 sampel. Maka gelombang kita akan memiliki bentuk yang sangat berbeda:



Tentu saja, ini tidak sepenuhnya akurat, karena Anda dapat melewati puncak volume yang mungkin Anda butuhkan, jadi Anda dapat mencoba bukan nilai rata-rata, tetapi maksimum dari segmen ini. Ini akan memberikan gambaran yang sedikit berbeda, tetapi puncak Anda tidak akan hilang.


Bersiap menerima audio


Mari kita tentukan keakuratan sampel kami sebagai kualitas, dan jumlah laporan akhir sebagai sampleCount.


 int quality = 100; int sampleCount = 0; sampleCount = freq / quality; 

Contoh penghitungan semua angka akan di bawah.


Selanjutnya, kita perlu mengambil sampel sendiri. Ini dapat dilakukan dari klip audio menggunakan metode GetData .


 public bool GetData(float[] data, int offsetSamples); 

Metode ini mengambil array ke mana ia menulis sampel. offsetSamples - parameter yang bertanggung jawab untuk titik awal membaca array data. Jika Anda membaca array dari awal, maka harus ada nol.


Untuk merekam sampel, kita perlu menyiapkan array untuknya. Misalnya, seperti ini:


 float[] samples; float[] waveFormArray; //      samples = new float[myAudio.clip.samples * myAudio.clip.channels]; 

Mengapa kami mengalikan panjangnya dengan jumlah saluran? Sekarang saya akan memberitahu ...


Informasi Saluran Audio Unity


Banyak orang tahu bahwa dalam suara kita biasanya menggunakan dua saluran: kiri dan kanan. Seseorang tahu bahwa ada sistem 2.1, serta 5.1, 7.1 di mana sumber suara mengelilingi dari semua sisi. Tema saluran dijelaskan dengan baik di wiki . Bagaimana cara kerjanya di Unity?


Saat mengunduh file, saat membuka klip, Anda dapat menemukan gambar berikut:



Hanya ditampilkan di sini bahwa kami memiliki dua saluran, dan Anda bahkan dapat melihat bahwa mereka berbeda satu sama lain. Unity merekam sampel saluran-saluran ini satu demi satu. Ternyata gambar ini:
[L1,R1,L2,R2,L3,R3,L4,R4,L5,R5,L6,R6,L7,R7,L8,R8...]


Itu sebabnya kita membutuhkan ruang dua kali lebih banyak dalam array daripada hanya untuk jumlah sampel.


Jika Anda memilih opsi klip Force To Mono, saluran akan menjadi satu dan semua suara akan berada di tengah. Pratinjau wave Anda akan segera berubah.




Terima data audio


Inilah yang kami dapatkan:


 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); //  ,    .       waveFormArray = new float[(samples.Length / sampleCount)]; //             for (int i = 0; i < waveFormArray.Length; i++) { waveFormArray[i] = 0; for (int j = 0; j < sampleCount; j++) { //Abs     ""    . .  waveFormArray[i] += Mathf.Abs(samples[(i * sampleCount) + j]); } waveFormArray[i] /= sampleCount; } } 

Total, jika trek berjalan 10 detik dan ini adalah dua saluran, maka kita mendapatkan yang berikut:


  • Jumlah sampel dalam klip (myAudio.clip.sample) = 44100 * 10 = 441000
  • Susunan sampel untuk dua saluran panjang (sampel. Panjang) = 441000 * 2 = 882000
  • Jumlah laporan (sampleCount) = 44100/100 = 441
  • Panjang array akhir = sampel. Panjang / sampleCount = 2000

Sebagai hasilnya, kami akan bekerja dengan 2000 poin, yang cukup bagi kami untuk menggambar gelombang. Sekarang Anda perlu memasukkan imajinasi dan berpikir tentang bagaimana kami dapat menggunakan data ini.


Rendering Informasi Audio


Buat trek audio sederhana menggunakan alat Debug


Seperti yang diketahui banyak orang, Unity memiliki cara yang nyaman untuk menampilkan semua jenis informasi Debug. Pengembang cerdas berdasarkan pada alat-alat ini dapat membuat, misalnya, ekstensi yang sangat kuat untuk editor. Kasus kami menunjukkan penggunaan metode Debug yang sangat tidak lazim.


Untuk menggambar, kita perlu garis. Kita dapat melakukannya dengan bantuan vektor yang akan dibuat dari nilai-nilai array kita. Harap dicatat, untuk membuat bentuk audio cermin yang indah, kita perlu "merekatkan" dua bagian dari visualisasi kita.


 for (int i = 0; i < waveFormArray.Length - 1; i++) { //      Vector3 upLine = new Vector3(i * .01f, waveFormArray[i] * 10, 0); //      Vector3 downLine = new Vector3(i * .01f, -waveFormArray[i] * 10, 0); } 

Selanjutnya, cukup gunakan Debug.DrawLine untuk menggambar vektor kita. Warna apa pun dapat memilih. Semua metode ini harus dipanggil dalam Pembaruan, jadi kami akan memperbarui informasi setiap frame.


 Debug.DrawLine(upLine, downLine, Color.green); 

Jika mau, Anda dapat menambahkan "slider" yang akan menunjukkan posisi trek saat ini sedang diputar. Informasi ini dapat diperoleh dari bidang "AudioSource.timeSamples".


 private float debugLineWidth = 5; // ""  .       int currentPosition = (myAudio.timeSamples / quality) * 2; Vector3 drawVector = new Vector3(currentPosition * 0.01f, 0, 0); Debug.DrawLine(drawVector - Vector3.up * debugLineWidth, drawVector + Vector3.up * debugLineWidth, Color.white); 

Total, ini skrip kami:


 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>(); //  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; } } private void Update() { for (int i = 0; i < waveFormArray.Length - 1; i++) { //      Vector3 upLine = new Vector3(i * 0.01f, waveFormArray[i] * 10, 0); //      Vector3 downLine = new Vector3(i * 0.01f, -waveFormArray[i] * 10, 0); // Debug  Debug.DrawLine(upLine, downLine, Color.green); } // ""  .       int currentPosition = (myAudio.timeSamples / quality) * 2; Vector3 drawVector = new Vector3(currentPosition * 0.01f, 0, 0); Debug.DrawLine(drawVector - Vector3.up * debugLineWidth, drawVector + Vector3.up * debugLineWidth, Color.white); } } 

Dan inilah hasilnya:



Buat soundscape yang halus dengan PolygonCollider2D


Sebelum melanjutkan ke bagian ini, saya ingin mencatat yang berikut: tentu saja, mengemudi di sepanjang trek yang dihasilkan dari musik itu menyenangkan, tetapi dari sudut pandang gameplay itu praktis tidak berguna. Dan inilah alasannya:


  1. Agar lintasan dapat dilewati, kita perlu memuluskan data kita. Semua puncak menghilang dan Anda praktis berhenti "merasakan musik Anda"
  2. Biasanya, trek musik sangat dikompresi dan mewakili batu bata suara, yang kurang cocok untuk game 2D.
  3. Masalah kecepatan transportasi kami yang belum terselesaikan, yang harus sesuai untuk kecepatan lintasan. Saya ingin mempertimbangkan masalah ini di artikel selanjutnya.

Oleh karena itu, sebagai percobaan, jenis generasi ini cukup lucu, tetapi sulit untuk membuat fitur gameplay yang nyata berdasarkan itu. Bagaimanapun, kami melanjutkan.


Jadi, kita perlu membuat PolygonCollider2D menggunakan data kami. Ini mudah dilakukan. PolygonCollider2D memiliki bidang poin publik yang menerima Vector2 []. Pertama, kita perlu mentransfer poin kita ke vektor dari tipe yang diinginkan. Mari kita membuat fungsi untuk menerjemahkan array sampel kami ke dalam array vektor:


 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; } 

Setelah itu, cukup serahkan array vektor yang dihasilkan ke collider:


 path = CreatePath(waveFormArray); poly.points = path; 

Kami melihat hasilnya. Ini adalah awal dari lagu kami ... hmm ... itu tidak terlihat lumayan (jangan berpikir tentang visualisasi, komentar akan datang nanti).



Kami memiliki bentuk audio yang terlalu tajam, sehingga trek keluar aneh. Perlu menghaluskannya. Di sini kita menggunakan algoritma rata-rata bergerak. Anda dapat membaca lebih lanjut tentang itu di Habr, di artikel Algoritma Rata-Rata Bergerak (Simple Moving Average) .


Di Unity, algoritma ini diterapkan sebagai berikut:


 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; } 

Kami memodifikasi pembuatan jalur kami:


 float[] avgArray = MovingAverage(frameSize, waveFormArray); path = CreatePath(avgArray); poly.points = path; 

Memeriksa ...



Sekarang lagu kami terlihat cukup normal. Saya menggunakan lebar jendela 10. Anda dapat memodifikasi parameter ini untuk memilih smoothing yang Anda butuhkan.


Berikut ini skrip lengkap untuk bagian ini:


 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; } //  ,    frameSize float[] avgArray = MovingAverage(frameSize, waveFormArray); path = CreatePath(avgArray); poly.points = path; } 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; } 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; } } 

Seperti yang saya katakan di awal bagian, dengan smoothing ini, kami berhenti merasakan trek, di samping itu, kecepatan mesin tidak terikat dengan kecepatan musik (BPM). Kami akan menganalisis masalah ini di bagian selanjutnya dari rangkaian artikel ini. Selain itu, di sana kami akan menyentuh pada topik spesial. efek di bawah irama. Omong-omong, saya mengambil mesin tik dari aset gratis ini.


Mungkin banyak dari Anda, melihat screenshot, bertanya-tanya bagaimana saya menggambar trek itu sendiri? Bagaimanapun, colliders tidak terlihat.


Saya menggunakan kebijaksanaan Internet dan menemukan cara di mana Anda dapat mengubah collider poligon menjadi mesh yang dapat Anda gunakan untuk bahan apa pun, dan pembuat baris akan membuat garis besar yang bergaya. Metode ini dijelaskan secara rinci di sini . Triangulator Anda dapat mengambil Unity Community .


Penyelesaian


Apa yang kami pelajari dalam artikel ini adalah sketsa dasar untuk permainan musik. Ya, dalam bentuk ini, sejauh ini, sedikit jelek, tetapi Anda dapat dengan aman mengatakan "Guys, saya membuat mesin berjalan di sepanjang trek audio!". Untuk menjadikan ini permainan nyata, Anda harus berusaha keras. Berikut adalah daftar yang dapat kita lakukan di sini:


  1. Ikat kecepatan mesin ke trek BPM. Pemain hanya bisa mengendalikan kemiringan mobil, tetapi tidak kecepatan. Maka musik akan terasa jauh lebih kuat selama kursus.
  2. Buat sedikit detektor dan tambahkan spesial. efek yang akan bekerja di bawah irama. Selain itu, Anda dapat menambahkan animasi ke tubuh mobil, yang akan memantul pada ketukan. Itu semua tergantung pada imajinasi Anda.
  3. Alih-alih bergerak rata-rata, Anda perlu lebih kompeten memproses trek dan mendapatkan berbagai data sehingga puncak tidak hilang, tetapi mudah untuk membangun jejak.
  4. Yah, dan, tentu saja, Anda perlu membuat permainannya menarik. Anda dapat menempatkan sedikit koin di setiap pukulan, menambahkan zona bahaya, dll.

Kami akan mempelajari semua ini dan banyak lagi di bagian-bagian yang tersisa dari seri artikel ini. Terima kasih sudah membaca!

Source: https://habr.com/ru/post/id432134/


All Articles