音楽ゲームのリズム同期

画像

私は最近、Unityでビートボックス音楽ゲームBoots-Cutの仕事を始めました。 ゲームの基本的な仕組みをプロトタイピングする過程で、ノートと音楽を正しく同期させることは非常に難しいことがわかりました。 このトピックに関するインターネット上の記事はかなりあります。 したがって、私の記事では、音楽ゲーム(特にUnity)の開発に関する最も重要なヒントを提供しようとします。

次の3つの側面が最も重要であることが判明しました。


これを考慮に入れて仕事に取り掛かります!

メインクラス


SongManagerクラスを作成して、曲内の位置を追跡し、ノートを作成し、その他の曲管理機能を作成する必要があります。

位置追跡


すべての音楽ゲームでは、作成すべきノートを知るために、曲の位置を追跡する必要があります。 以下は、曲の位置を追跡するために必要なフィールドです。

 //    ( ) float songPosition; //    ( ) float songPosInBeats; //  float secPerBeat; //  ( )     float dsptimesong; 

Start()関数でこれらのフィールドを初期化します。

 void Start() { //      // bpm   secPerBeat = 60f / bpm; //    dsptimesong = (float) AudioSettings.dspTime; //  GetComponent<AudioSource>().Play(); } 

便宜上、 bpmsecPerBeat変換しsecPerBeat 。 後でsecPerBeatを使用して、ビート内の曲の位置を計算します。これは、ノートを作成するために非常に重要です。

さらに、曲の開始時間をdsptimesongます。 Time.timeSinceLevelLoad各フレームでのみ更新され、 AudioSettings.dspTimeオーディオタイマーであるためより頻繁に更新されるため、 AudioSettings.dspTimeではなくAudioSettings.dspTimeを使用します。 曲のテンポを維持するには、オーディオタイマーを使用する必要があります。 これにより、フレームの更新とオーディオの更新の時間差による遅延を回避できます。

Update()関数は、 AudioSettings.dspTimeを使用して曲の位置を計算しAudioSettings.dspTime

 void Update() { //    songPosition = (float) (AudioSettings.dspTime - dsptimesong); //    songPosInBeats = songPosition / secPerBeat; } 

現在のAudioSettings.dspTimeから曲の開始時間( dsptimesong )を引くことにより、位置を秒単位で計算します。 数秒で位置を取得しましたが、音楽の世界では、音符はビートで記録されます。 したがって、秒単位の位置を拍単位の位置に変換することをお勧めします。 songPositionsecPerBeat (second /(second / beat))でsongPositionして、ビートの位置を取得します。

写真を見てください:



ビート内のノートの位置:1、2、2.5、3、3.5、4.5、およびビートの持続時間は0.5秒です。 したがって、歌の開始から1.75秒( songPosition == 1.75 )が経過した場合、位置1.75songPosition )/ 0.5secPerBeat )= 3.5ビートにあることがsecPerBeat 、3.5ビートのノートを作成する必要があります。

曲情報


歌に関する情報を記録したフィールドに進みましょう。

 //    float bpm; //      float[] notes; // ,     int nextIndex = 0; 

簡単にするために、1曲のみのノート( Guitar Hero Mobileで3曲、 Taikono Tatsujinで 1曲のみ)を使用して曲をデモします。

bpmは、1分あたりの拍数です。 secPerBeat 、便宜上、これらはsecPerBeat変換されsecPerBeat

notesは、ビート内のすべてのノートの位置が格納される配列です。 たとえば、図に示すノートの場合、ノート配列には{1f, 2f, 2.5f, 3f, 3.5f, 4.5f}が含まれます。



最後に、 nextIndexは配列をトラバースするために必要な整数です。 作成される次のノートが曲の最初のノートになるため、0に初期化されます。 メモを作成すると、 nextIndexカウンターnextIndex 1増加します。

メモを作成する


Update()関数でメモを作成するかどうかを決定します。 ただし、最初に表示されるストローク数を事前に決定する必要があります。

たとえば、次のトラックの場合:



ストロークの現在の位置は1ですが、ストライク3はすでに作成されています。 これは、3つのヒットが事前に表示されることを意味します。

songPosInBeats = songPosition / secPerBeat;下に追加しsongPosInBeats = songPosition / secPerBeat; 、次の行:

 if (nextIndex < notes.Length && notes[nextIndex] < songPosInBeats + beatsShownInAdvance) { Instantiate( /*   */ ); //   nextIndex++; } 

まず、曲にノートが残っているかどうかを確認する必要があります( nextIndex < notes.Length )。 まだ音符がある場合は、次の音符を作成する必要がある場所で歌がビートに当たるかどうかを確認します( notes[nextIndex] < songPosInBeats + beatsShownInAdvance )。 届いたら、メモを作成し、 nextIndexを増やして、作成する次のメモを追跡します。

音符の動き


最後に、作成したノートを曲のテンポに合わせて移動する方法について説明します。 「時間差に従って各フレームのノートを更新しないで、それらを補間する」という項目を思い出せば、これは非常に簡単です。

次の理由により、曲の位置ごとに常に動きを更新します。

  1. オーディオタイマーにはフレームタイマーとの時間差があります
  2. ビートは正確に2つのフレームの中央にある可能性があります(これは時間差につながります)

では、ノートをどのように移動しますか? 補間によって!

簡単にするために、 MusicNoteクラスのすべてのコードを切り取り、各ノートを移動するUpdate()関数のみを残しUpdate()

 //   void Update() { transform.position = Vector2.Lerp( SpawnPos, RemovePos, (BeatsShownInAdvance - (beatOfThisNote - songPosInBeats)) / BeatsShownInAdvance ); } 

以下の図では、これがはっきりと見えます。



おわりに


音楽ゲームのプログラミングの基本について話しました。 これらの原則に従って、同期を使用してゲームを作成できます。 複数のトラックがあるゲームでは、 notesネストされた配列を作成できます。 notes削除は、削除行に対する位置を確認することによって実行されます。長時間のノートは、最初と最後のビートを追跡することによって実装されます。

記事を読んでくれてありがとう、それが役に立つことを願っています。 私自身の音楽ゲームBoots-Cutsは来年準備ができていますので、お楽しみに。

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


All Articles