私は最近、Unityでビートボックス音楽ゲームBoots-Cutの仕事を始めました。 ゲームの基本的な仕組みをプロトタイピングする過程で、ノートと音楽を正しく同期させることは非常に難しいことがわかりました。 このトピックに関するインターネット上の記事はかなりあります。 したがって、私の記事では、音楽ゲーム(特にUnity)の開発に関する最も重要なヒントを提供しようとします。
次の3つの側面が最も重要であることが判明しました。
-
AudioSettings.dspTime
代わりにTime.timeSinceLevelLoad
を使用して、曲の位置を追跡します。 - 曲の位置を常に使用して、動きを更新する必要があります。
- 時間差に従って各フレームのノートを更新せず、それらを補間します。
これを考慮に入れて仕事に取り掛かります!
メインクラス
SongManager
クラスを作成して、曲内の位置を追跡し、ノートを作成し、その他の曲管理機能を作成する必要があります。
位置追跡
すべての音楽ゲームでは、作成すべきノートを知るために、曲の位置を追跡する必要があります。 以下は、曲の位置を追跡するために必要なフィールドです。
// ( ) float songPosition; // ( ) float songPosInBeats; // float secPerBeat; // ( ) float dsptimesong;
Start()
関数でこれらのフィールドを初期化します。
void Start() { // // bpm secPerBeat = 60f / bpm; // dsptimesong = (float) AudioSettings.dspTime; // GetComponent<AudioSource>().Play(); }
便宜上、
bpm
を
secPerBeat
変換し
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
)を引くことにより、位置を秒単位で計算します。 数秒で位置を取得しましたが、音楽の世界では、音符はビートで記録されます。 したがって、秒単位の位置を拍単位の位置に変換することをお勧めします。
songPosition
を
secPerBeat
(second /(second / beat))で
songPosition
して、ビートの位置を取得します。
写真を見てください:
ビート内のノートの位置:1、2、2.5、3、3.5、4.5、およびビートの持続時間は0.5秒です。 したがって、歌の開始から1.75秒(
songPosition == 1.75
)が経過した場合、位置1.75 (
songPosition
)/ 0.5 (
secPerBeat
)= 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
を増やして、作成する次のメモを追跡します。
音符の動き
最後に、作成したノートを曲のテンポに合わせて移動する方法について説明します。 「時間差に従って各フレームのノートを更新しないで、それらを補間する」という項目を思い出せば、これは非常に簡単です。
次の理由により、曲の位置ごとに常に動きを更新します。
- オーディオタイマーにはフレームタイマーとの時間差があります
- ビートは正確に2つのフレームの中央にある可能性があります(これは時間差につながります)
では、ノートをどのように移動しますか? 補間によって!
簡単にするために、
MusicNote
クラスのすべてのコードを切り取り、各ノートを移動する
Update()
関数のみを残し
Update()
。
// void Update() { transform.position = Vector2.Lerp( SpawnPos, RemovePos, (BeatsShownInAdvance - (beatOfThisNote - songPosInBeats)) / BeatsShownInAdvance ); }
以下の図では、これがはっきりと見えます。
おわりに
音楽ゲームのプログラミングの基本について話しました。 これらの原則に従って、同期を使用してゲームを作成できます。 複数のトラックがあるゲームでは、
notes
ネストされた配列を作成できます。
notes
削除は、削除行に対する位置を確認することによって実行されます。長時間のノートは、最初と最後のビートを追跡することによって実装されます。
記事を読んでくれてありがとう、それが役に立つことを願っています。 私自身の音楽ゲームBoots-Cutsは来年準備ができていますので、お楽しみに。