質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

8421閲覧

【Unity】音ゲー制作で曲とテンポずれてしまう

Ainey

総合スコア11

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

1クリップ

投稿2020/03/21 14:45

前提・実現したいこと

初めてUnityで音ゲーを作ろうとしておりますが分かりません...。

まず、試しに曲とテンポがマッチするようにBPMを元にメトロノームを作ろうとしましたが、
どうしてもズレが生じてしまいます。原因はおそらく、Update関数で処理をしているため
不安定な状況になっており、処理がおいついていないのだと思います。

そこで、とあるサイトのソースコードを元に試したところ
完璧に合うようになりました。

しかし、そのコードがどのような仕組みなのか、なぜ合ったのかイマイチ理解できない部分があるので
解説していただけないでしょうか...?また、他に良い方法が何かあれば教えていただけると幸いです..。

発生している問題・エラーメッセージ

曲とテンポにずれが生じてしまう

解説して頂きたいソースコード

C#

1public class GoodMetronome : MonoBehaviour { 2 3 [SerializeField] AudioSource _ring; 4 5 double _bpm = 140d; 6 double _metronomeStartDspTime; 7 double _buffer = 2 / 60d; 8 9 void Start() { 10 _metronomeStartDspTime = AudioSettings.dspTime; 11 } 12 13 void FixedUpdate() { 14 var nxtRng = NextRingTime(); 15 16 if (nxtRng < AudioSettings.dspTime + _buffer) { 17 _ring.PlayScheduled(nxtRng); 18 } 19 } 20 21 double NextRingTime() { 22 var beatInterval = 60d / _bpm; 23 var elapsedDspTime = AudioSettings.dspTime - _metronomeStartDspTime; 24 var beats = System.Math.Floor(elapsedDspTime / beatInterval); 25 26 return _metronomeStartDspTime + (beats + 1d) * beatInterval; 27 } 28} 29

上記ソースコードを参考に作成したソースコード

C#

1using System.Collections; 2using System.Collections.Generic; 3using System.IO; 4using UnityEngine; 5using UnityEngine.UI; 6 7public class GameControl : MonoBehaviour{ 8 9 //譜面情報 10 private List<string[]> SONG_SCORE = new List<string[]>(); 11 private TextAsset SONG_FILE; 12 private string SONG_TITLE; 13 private string SONG_ARTIST; 14 private float SONG_BPM; 15 16 private double _metronomeStartDspTime; 17 private double _buffer = 2 / 60d; 18 private int Count = 0; 19 20 //音楽ファイル 21 private AudioSource[] BGM; 22 23 //譜面情報TEXTオブジェクト 24 private Text[] TITLE_A; 25 26 private bool test = false; 27 28 // Start is called before the first frame update 29 void Start() 30 { 31 //譜面情報を取得する 32 GetGameScore(); 33 34 //テキスト取得 35 TITLE_A = new Text[4]; 36 TITLE_A[0] = GameObject.Find("TITLE").GetComponent<Text>(); 37 TITLE_A[1] = GameObject.Find("ARTIST").GetComponent<Text>(); 38 TITLE_A[2] = GameObject.Find("BPM").GetComponent<Text>(); 39 TITLE_A[3] = GameObject.Find("Beat").GetComponent<Text>(); 40 41 //テキスト編集 42 TITLE_A[0].text = "曲名;" + SONG_TITLE; 43 TITLE_A[1].text = "アーティスト:" + SONG_ARTIST; 44 TITLE_A[2].text = "BPM:" + SONG_BPM; 45 46 //BGMとSEを取得 47 BGM = this.GetComponents<AudioSource>(); 48 49 //開始地点(OFF_SET) 50 BGM[0].time = 0.6f; 51 52 53 //確認用 54 _metronomeStartDspTime = AudioSettings.dspTime; 55 56 57 } 58 59 // Update is called once per frame 60 void FixedUpdate() 61 { 62 63 double nxtRng = NextRingTime(); 64 65 if (nxtRng < AudioSettings.dspTime + _buffer) 66 { 67 if (test == false) { BGM[0].Play(); test = true; } 68 BGM[1].PlayScheduled(nxtRng); 69 } 70 71 } 72 73 //CSVファイルから譜面情報を取得 74 void GetGameScore() 75 { 76 SONG_FILE = Resources.Load("Score/TestMusic") as TextAsset; 77 StringReader reader = new StringReader(SONG_FILE.text); 78 79 while (reader.Peek() > -1) 80 { 81 string line = reader.ReadLine(); 82 SONG_SCORE.Add(line.Split(',')); 83 } 84 85 //TITLE、ARTISを抽出 86 SONG_TITLE = SONG_SCORE[0][0].Replace("TITLE:",""); 87 SONG_ARTIST = SONG_SCORE[1][0].Replace("ARTIST:", ""); 88 SONG_BPM = float.Parse(SONG_SCORE[2][0].Replace("BPM:", "")); 89 } 90 91 double NextRingTime() 92 { 93 double beatInterval = 60d / SONG_BPM; 94 double elapsedDspTime = AudioSettings.dspTime - _metronomeStartDspTime; 95 double beats = System.Math.Floor(elapsedDspTime / beatInterval); 96 97 return _metronomeStartDspTime + (beats + 1d) * beatInterval; 98 } 99 100}

補足情報(FW/ツールのバージョンなど)

Unity 2019.3.3f1

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

izmktr

2020/03/23 02:21

うまくいくコードがうまくいくのは当たり前です 自分のコードを出してうまく行かないのはなぜかとか、 このコードではこんな挙動をすると考えたのにそうならないのはなぜか、とかが本来の質問だと思います
Ainey

2020/03/23 14:34

izmktrさん まったくその通りだと思います。今回はこのコードがどういうロジックで動いているのかを自分で考えてから、ここで何故これを代入しているのか、何故こういう動きになるのかなど質問要点をまとめてから投稿するべきでした。勿論自分でも読み取ってはみましたが丸投げするような形となってしまったので以後気を付けたいと思います。回答ありがとうございました。
guest

回答1

0

ベストアンサー

■FixedUpdateの中身
nxtRng→「次にメトロノームが鳴る時刻」
AudioSettings.dspTime + _buffer→「オーディオシステム上の現在時刻+バッファ時間」
_ring.PlayScheduled(nxtRng);→「次にメトロノームが鳴る時刻」に音を鳴らす
(※「時刻」とは「ゲーム開始時からの経過秒」を指す)

つまり
nxtRngが30秒、現在時刻(AudioSettings.dspTime)が29.98秒だった場合、バッファ(_buffer)が約0.03秒なので
if (30.00 < 30.01)となるので条件を満たし、「現在時刻が30秒になったら(つまり0.02秒後に)音再生」となります。

なおUpdateではなくFixedUpdateを使っているのは、
Updateがゲームのフレームレートに依存して実行される(0.033秒毎だったり0.016秒毎だったり、環境と負荷状況によりリアルタイムで変わる)のに対し、
FixedUpdateが常に一定時間毎に実行される(デフォルトだと0.02秒毎)為です。
今回は確実に一定時間毎に実行したいのでFixedUpdateが使われています。


■NextRingTimeの中身
前述の通り、「次にメトロノームが鳴る時刻(=次の拍の時刻)」を返します。
(※こちらも「時刻」とは「ゲーム開始時からの経過秒」を指す)

_bpm→BPM、つまり1分間に何拍あるか
beatInterval→1拍あたり何秒間隔か(60秒/BPM)
elapsedDspTime→現在時刻とスクリプト開始時刻の差
beats→現在時刻と開始時刻の間に何回の拍があったか

つまり
BPM140の場合、beatIntervalは約0.43秒。
スクリプト開始時刻が10秒、現在時刻が30秒だとすると差は20秒なので、現在までに約46.51回の拍を打ち済み。
求めたいのは次の拍、つまり47.51回目(beats + 1d)。
これを秒数に直す(* beatInterval)と約20.43秒、
スクリプト開始時刻に加算する(_metronomeStartDspTime +)と30.43秒なので
「次の拍の時刻」は30.43秒(つまり現在時刻から0.43秒後)と分かります。

(上記は分かりやすいよう小数第3位を四捨五入してるので結論がAudioSettings.dspTime + beatIntervalっぽく見えますが、実際はもっと小さい値を取り扱っているのでこのスクリプトでないとダメかと思います)

投稿2020/03/23 02:24

編集2020/03/23 02:25
sakura_hana

総合スコア11427

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Ainey

2020/03/23 14:33

ようやく理解できました。非常に分かりやすく、モヤモヤしてた気持ちに花が咲いた気分です。感謝感激。 今回は頼ってしまいましたが、やはり仕組みを理解した上でプログラムが正常に動くと楽しいものですね。 引き続きゲーム制作頑張りたいと思います。回答ありがとうございました。
sakura_hana

2020/03/25 01:33

ちなみにですが、変数や関数の意味を理解して日本語に置き換えるのが第一歩です(というか私は今回そうしました)。 私も「AudioSettings.dspTime」は初見だったのでリファレンスとGoogle先生で把握。 BPMって厳密には何か分かってなかったのでこちらも調査。 あとはスクリプトを追っていけば「何をしているのか」までは分かると思います。 その上で「何故こうしているのか」が分からなければ質問、という形にすると丸投げとも取られなくて済むかなと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問