Unity3Dでのサウンドおよび音楽ベースの環境生成。 パート2.音楽から2Dトラックを作成する

注釈



みなさんこんにちは。 比較的最近、私はUnity3Dのサウンドと音楽に基づいた環境の生成という記事を書きました。そこでは、 音楽に基づいてコンテンツを生成するメカニズムを使用するゲームの例をいくつか紹介し、そのようなゲームの基本的な側面についても話しました。 記事には実質的にコードはなく、続編があると約束しました。 そして、それはあなたの目の前です。 今回は、Hill Climbスタイルの2Dレース用のトラックを音楽から作成しようとします。 何が得られるか見てみましょう..













はじめに



この一連の記事は、初心者デベロッパーと、最近サウンドを使い始めたばかりの開発者向けに設計されていることを思い出してください。 あなたが心の中で高速フーリエ変換をしているなら、おそらく退屈するでしょう。







今日のロードマップは次のとおりです。







  1. 離散化とは何かを検討してください。
  2. Audio Clip Unityから取得できるデータを調べる
  3. このデータをどのように使用できるかを理解してください。
  4. このデータから生成できるものを見つけてください。
  5. このすべてからゲームを作成する方法を学ぶ(まあ、またはゲームに似たもの)


さあ、行こう!







アナログシンハラ語の離散化



多くの人が知っているように、デジタルシステムで信号を使用するには、信号を変換する必要があります。 変換ステップの1つは信号サンプリングで、アナログ信号は部分(一時レポート)に分割されます。その後、各レポートには選択された瞬間の振幅値が割り当てられます。







文字Tはサンプリング周期を示します。 期間が短いほど、信号変換はより正確になります。 しかし、ほとんどの場合、彼らは逆について話します:サンプルレート(これはF = 1 / Tであることが論理的です)。 電話の信号には8,000 Hzで十分です。たとえば、DVD-Audio形式のオプションの1つには、192,000 Hzのサンプリング周波数が必要です。 デジタルレコーディング(ゲームエディター、音楽エディター)の標準は44 100 Hzです。これはCDオーディオの周波数です。







振幅の数値は、いわゆるサンプルに保存されており、それらを使用して作業します。 サンプルの値はfloatであり、-1〜1の範囲で指定できます。簡略化すると、次のようになります。













音波のレンダリング(静的)



基本情報



波形(またはオーディオ形式、および一般的な人々-「魚」)は、時間の経過に伴う音声信号の視覚的表現です。 波形は、アクティブフェーズが発生するサウンド内のポイントと減衰が発生する場所を示します。 多くの場合、波形は各チャネルごとに、たとえば次のように個別に表示されます。













既にAudioSourceと作業中のスクリプトがあると想像してください。 Unityが提供できるものを見てみましょう。







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





レポート数を選択



先に進む前に、サウンドのレンダリングの深さについて少し話す必要があります。 1秒あたり44100 Hzのサンプリング周波数で、44100レポートを処理できます。 10秒間のトラックをレンダリングする必要があるとしましょう。 各レポートをピクセル幅の線で描画します。 波形は441,000ピクセルの長さになります。 非常に長く、長く、ほとんど理解されていない音波が得られます。 しかし、その中には特定の各レポートがあります! そして、どのように描画しても、システムをひどくロードします。













プロのオーディオソフトウェアを作成していない場合、そのような精度は必要ありません。 一般的なオーディオ画像の場合、すべてのサンプルをより大きな期間に分割し、たとえば各100サンプルの平均を取ることができます。 そうすると、ウェーブは非常に明確な形式になります。













もちろん、必要なボリュームピークをスキップできるため、これは完全に正確ではありません。そのため、このセグメントの平均値ではなく最大値を試すことができます。 これにより、わずかに異なる画像が得られますが、ピークは消えません。







オーディオを受信する準備



サンプルの精度を品質として定義し、レポートの最終数をsampleCountとして定義しましょう。







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





すべての数値を計算する例を以下に示します。







次に、サンプルを自分で取得する必要があります。 これは、 GetDataメソッドを使用してオーディオクリップから実行できます。







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





このメソッドは、サンプルを書き込む配列を受け取ります。 offsetSamples-データ配列の読み取りの開始点を担当するパラメーター。 配列を最初から読み取る場合、ゼロが存在するはずです。







サンプルを記録するには、サンプル用の配列を準備する必要があります。 たとえば、次のように:







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





なぜ長さをチャネル数で乗算したのですか? 今教えて...







Unityのオーディオチャネル情報



多くの人は、音では通常2つのチャンネルを使用することを知っています:左と右。 2.1システムと、すべての側面から音源を囲む5.1、7.1があることを誰かは知っています。 チャンネルのテーマはwikiで詳しく説明されています 。 Unityでこれはどのように機能しますか?







ファイルをダウンロードするとき、クリップを開くとき、次の画像を見つけることができます。













ここでは、2つのチャネルがあることを示していますが、それらは互いに異なることさえわかります。 Unityはこれらのチャンネルのサンプルを次々と記録します。 この写真は次のとおりです。

[L1R1L2R2L3R3L4R4L5R5L6R6L7R7L8R8...]







そのため、サンプル数だけでなく、配列に2倍のスペースが必要です。







[モノラルに強制]クリップオプションを選択すると、チャンネルは1つになり、すべてのサウンドが中央になります。 ウェーブのプレビューはすぐに変更されます。



















音声を受信する



取得するものは次のとおりです。







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





合計で、トラックが10秒で2チャンネルの場合、次のようになります。









その結果、2000ポイントで作業することになり、波を引くのに十分です。 ここで、想像力を盛り込み、このデータをどのように使用できるかを考える必要があります。







オーディオ情報のレンダリング



デバッグツールを使用して簡単なオーディオトラックを作成する



多くの人が知っているように、Unityにはあらゆる種類のデバッグ情報を表示する便利な手段があります。 これらのツールに基づいたインテリジェントな開発者は、たとえば、エディターの非常に強力な拡張機能を作成できます。 このケースでは、Debugメソッドの非常に非典型的な使用方法を示しています。







描画するには、線が必要です。 配列の値から作成されるベクトルの助けを借りてそれを行うことができます。 美しいミラーオーディオフォームを作成するには、視覚化の2つの半分を「接着」する必要があります。







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





次に、Debug.DrawLineを使用してベクターを描画します。 任意の色を選択できます。 これらのメソッドはすべてUpdateで呼び出す必要があるため、フレームごとに情報を更新します。







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





必要に応じて、再生中のトラックの現在の位置を示す「スライダー」を追加できます。 この情報は、「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);
      
      





合計、ここにスクリプトがあります:







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





結果は次のとおりです。













PolygonCollider2Dでスムーズなサウンドスケープを作成する



このセクションに進む前に、次の点に注意したいと思います。もちろん、音楽から生成されたトラックに沿って運転するのは楽しいですが、ゲームプレイの観点からは、実際には役に立ちません。 理由は次のとおりです。







  1. トラックを通過可能にするには、データを平滑化する必要があります。 すべてのピークが消え、実質的に「音楽を感じる」のをやめる
  2. 通常、音楽トラックは高度に圧縮されており、2Dゲームにはあまり適していません。
  3. 輸送の速度に関する未解決の問題。これはトラックの速度に合うはずです。 この問題については、次の記事で検討します。


したがって、実験として、このタイプの生成は非常に面白いですが、それに基づいて実際のゲームプレイ機能を作成することは困難です。 いずれにせよ、私たちは続けます。







そのため、データを使用してPolygonCollider2Dを作成する必要があります。 これは簡単です。 PolygonCollider2Dには、Vector2 []を受け入れるパブリックポイントフィールドがあります。 まず、ポイントを目的の形式のベクトルに転送する必要があります。 サンプルの配列をベクトル配列に変換する関数を作成しましょう:







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





その後、結果のベクトルの配列をコライダーに渡すだけです:







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





結果を見ます。 これが私たちのトラックの始まりです...うーん...それはあまり通用しそうに見えません(まだ視覚化について考えないでください、コメントは後で来るでしょう)。













オーディオ形式がシャープすぎるため、トラックが奇妙になります。 それを滑らかにする必要があります。 ここでは、移動平均アルゴリズムを使用します。 詳細については、Habreの記事Simple Moving Average Algorithmを参照してください。







Unityでは、アルゴリズムは次のように実装されます。







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





パスの作成を変更します。







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





確認しています...













これで、トラックは非常に正常に見えます。 10のウィンドウ幅を使用しました。このパラメーターを変更して、必要なスムージングを選択できます。







このセクションの完全なスクリプトは次のとおりです。







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





セクションの冒頭で述べたように、このスムージングにより、トラックのフィーリングが停止します。さらに、マシンの速度は音楽の速度(BPM)に関係しません。 この一連の記事の次のパートで、この問題を分析します。 さらに、スペシャルのトピックについても触れます。 ビートの下の効果。 ところで、私はこの無料の資産からタイプライターを取りました。







おそらくスクリーンショットを見て、あなたの多くは、トラック自体をどのように描いたか疑問に思いましたか? 結局のところ、コライダーは表示されません。







私はインターネットの知恵を使用して、ポリゴンコライダーをメッシュに変換して、任意のマテリアルを割り当てることができ、ラインレンダラーがスタイリッシュなアウトラインを作成する方法を見つけました。 この方法については、 ここで詳しく説明しますUnity Communityに参加できる三角測量器。







完了



この記事で学んだことは、音楽ゲームの基本的なスケッチです。 はい、この形式ではこれまでのところ少しlittleいですが、「みんな、マシンをオーディオトラックに沿って移動させました!」と安全に言うことができます。 これを実際のゲームにするには、多くの努力をする必要があります。 ここでできることのリストです:







  1. マシンの速度をBPMトラックにバインドします。 プレーヤーは車の傾きのみを制御できますが、速度は制御できません。 その後、コース中に音楽がより強く感じられます。
  2. ビット検出器を作成し、スペシャルを追加します。 ビットによってトリガーされるエフェクト。 さらに、アニメーションを車体に追加して、ビートのビートでバウンスすることができます。 それはすべてあなたの想像力にかかっています。
  3. 移動平均ではなく、トラックをより適切に処理し、ピークが消えないようにデータの配列を取得する必要がありますが、トレースを作成するのは簡単でした。
  4. もちろん、ゲームプレイを面白くする必要があります。 ヒットごとにコインビットを配置したり、危険ゾーンを追加したりできます。


この一連の記事の残りの部分で、これらすべてを研究します。 読んでくれてありがとう!








All Articles