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

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

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

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

Unity

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

Q&A

解決済

1回答

4892閲覧

Unityの音ゲー制作でタイミングよく判定ラインにノーツを降らせたい

YDK

総合スコア63

C#

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

Unity

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

0グッド

1クリップ

投稿2021/05/09 05:56

編集2021/05/19 14:00

Jsonファイルを読み込んでノーツを生成することはできたのですが、
タイミングよく判定ラインに降らせる事ができませんでした。
譜面の読み方を学んだり、BPMの値によって1分間に何小節必要なのか、
一小節、一拍にか掛かる時間を計算したりして
道のり=速さ*時間の法則を使ってノーツから次のノーツの距離を測って生成したりしたのですが、
上手くいきませんでした。どなたかわかる方がいましたら回答よろしくお願いします。

追記
ObjectPoolクラスを追加しました。

C#

1/// <summary> 2/// ノートを生成するクラス 3/// </summary> 4public class NotesCreater : ObjectPool 5{ 6 /// <summary> タップノーツ </summary> 7 [SerializeField] GameObject tapNote; 8 /// <summary> ホールドノーツ </summary> 9 [SerializeField] GameObject holdNote; 10 /// <summary> フリックノーツ </summary> 11 [SerializeField] GameObject flickNote; 12 /// <summary> 最初にInstance化するObjectの数 </summary> 13 [SerializeField] int initNotesCount; 14 /// <summary> 追加でInstance化するObjectの数 </summary> 15 [SerializeField] int createNotesCount; 16 /// <summary> Noteの角度 </summary> 17 Quaternion angle = Quaternion.Euler(-30f, 0, 0); 18 [SerializeField] Transform judgmentLine; 19 20 LoadTest load = new LoadTest(); 21 MusicDTO.EditData music; 22 23 void Awake() 24 { 25 try 26 { 27 FileInfo file = new FileInfo(load.FilePath); 28 using (StreamReader sr = new StreamReader(file.OpenRead())) 29 { 30 music = JsonUtility.FromJson<MusicDTO.EditData>(sr.ReadToEnd()); 31 } 32 } 33 catch (FileNotFoundException e) 34 { 35 Debug.LogError("指定されたファイルが見つかりませんでした\n" + e); 36 } 37 38 AudioManager clip = GameObject.Find("MusicSource").GetComponent<AudioManager>(); 39 // 小節の数 40 float numberOfMeasures = music.BPM / 4f; 41 // 一小節に掛かる時間(秒) 42 float oneBarTime = 60f / numberOfMeasures; 43 // 一拍に掛かる時間(秒) => ノーツの間隔 44 float oneBeatTime = oneBarTime / 16f; 45 46 //Vector3 judgLinePos = judgmentLine.position; 47 for (int i = 0; i < music.notes.Count; i++) 48 { 49 float distance = Note.NoteSpeed * beatPerSecond; 50 Vector3 notePos = new Vector3(-3.8f + music.notes[i].block * 1.9f, 51 music.notes[i].num * distance / Mathf.Sqrt(3.0f), 52 music.notes[i].num * distance); 53 54 switch (music.notes[i].type) 55 { 56 case 1: 57 CreatePool(tapNote, notePos, angle); 58 break; 59 case 2: 60 CreatePool(holdNote, notePos, angle); 61 break; 62 case 3: 63 CreatePool(flickNote, notePos, angle); 64 break; 65 default: 66 break; 67 } 68 } 69 clip.Music.Play(); 70 } 71} 72 73//(music.notes[i].num * note.NoteSpeed) 74//(music.notes[i].num * note.NoteSpeed) 75//judgLinePos.y + ((music.notes[i].num - clip.Music.time) * music.notes[i].LPB) / Mathf.Sqrt(3.0f), 76//judgLinePos.z + (music.notes[i].num - clip.Music.time) * music.notes[i].LPB); 77//music.BPM / 60f 78//music.BPM / 60f

C#

1/// <summary> 2/// ノートを降らせるクラス 3/// </summary> 4[RequireComponent(typeof(Rigidbody))] 5public class Note : MonoBehaviour 6{ 7 Rigidbody rigid; 8 [SerializeField] static float noteSpeed = 4.0f; 9 public static float NoteSpeed { get { return noteSpeed; } } 10 11 void OnEnable() 12 { 13 noteSpeed = 4f * 158f / 60f; 14 rigid = GetComponent<Rigidbody>(); 15 rigid.useGravity = false; 16 rigid.velocity = Vector3.zero; 17 } 18 19 void FixedUpdate() 20 { 21 Vector3 angle = new Vector3(-30, 0, 0); 22 Vector3 direction = Quaternion.Euler(angle) * Vector3.back; 23 rigid.velocity = direction * noteSpeed; 24 } 25}

C#

1/// <summary> 2/// オブジェクトを生成して使い回すクラス 3/// </summary> 4public abstract class ObjectPool : MonoBehaviour 5{ 6 /// <summary> PoolするObject </summary> 7 GameObject poolObj; 8 /// <summary> PoolしたObjectを格納するList </summary> 9 List<GameObject> poolObjList; 10 11 /// <summary> 12 /// オブジェクトプールを作成 13 /// </summary> 14 /// <param name="obj"> Instance化するObject </param> 15 /// <param name="pos"> Instance化する位置 </param> 16 /// <param name="angle"> Instance化する角度 </param> 17 protected void CreatePool(GameObject obj, Vector3 pos, Quaternion angle) 18 { 19 poolObj = obj; 20 poolObjList = new List<GameObject>(); 21 22 var newObj = CreateNewObject(pos, angle); 23 //newObj.gameObject.SetActive(false); 24 poolObjList.Add(newObj); 25 } 26 27 /// <summary> 28 /// 未使用のObjectを探す 29 /// </summary> 30 /// <param name="pos"> Instance化する位置 </param> 31 /// <param name="angle"> Instance化する角度 </param> 32 /// <returns></returns> 33 protected GameObject GetUnusedObject(Vector3 pos, Quaternion angle) 34 { 35 // 使用中でないものを探す 36 foreach (var obj in poolObjList) 37 { 38 if (obj.gameObject.activeSelf == false) 39 { 40 obj.gameObject.SetActive(true); 41 return obj; 42 } 43 } 44 45 // 全て使用中だったら新しく作って返す 46 var newObj = CreateNewObject(pos, angle); 47 newObj.gameObject.SetActive(true); 48 poolObjList.Add(newObj); 49 return newObj; 50 } 51 52 /// <summary> 53 /// Objectが足らなかった場合、新しくInstance化する 54 /// </summary> 55 /// <param name="pos"> 位置 </param> 56 /// <param name="angle"> 角度 </param> 57 /// <returns> Instance化したObject </returns> 58 GameObject CreateNewObject(Vector3 pos, Quaternion angle) 59 { 60 var newObj = Instantiate(poolObj, pos, angle); 61 newObj.name = poolObj.name + (poolObjList.Count + 1); 62 return newObj; 63 } 64 65 /// <summary> 66 /// PoolしたObjectを全て削除する 67 /// </summary> 68 protected void DeleteAllPoolObject() 69 { 70 foreach (var poolObj in poolObjList) 71 { 72 Destroy(poolObj); 73 } 74 75 poolObjList.Clear(); 76 } 77}

C#

1/// <summary> 2 /// 譜面データを読み込むクラス 3 /// </summary> 4 public class LoadManager : MonoBehaviour 5 { 6 /// <summary> 譜面が保存されている場所 </summary> 7 string filePath = "/Users/sasuke/Documents/FonePaw/Notes/シャイニングスター.json"; 8 public string FilePath { get { return filePath; } } 9 /// <summary> 楽曲名 </summary> 10 public string Name { get; private set; } 11 /// <summary> レーンの総数 </summary> 12 public int MaxBlock { get; private set; } 13 /// <summary> 1分間に四分音符が何拍なるか </summary> 14 public float BPM { get; private set; } 15 /// <summary> 譜面の開始位置 </summary> 16 public int Offset { get; private set; } 17 /// <summary> Noteクラスのメンバ変数を保持する </summary> 18 public List<MusicDTO.Note> Notes { get; private set; } 19 /// <summary> 1拍の内にプレイカーソルが何ライン進むか </summary> 20 public List<int> LPB { get; private set; } = new List<int>(); 21 /// <summary> 拍子番号 </summary> 22 public List<int> Num { get; private set; } = new List<int>(); 23 /// <summary> レーンの番号 </summary> 24 public List<int> Block { get; private set; } = new List<int>(); 25 /// <summary> Notesの種類 </summary> 26 public List<NoteType> Type { get; private set; } = new List<NoteType>(); 27 /// <summary> ノートが判定ラインに重なるタイミング </summary> 28 public List<float> NoteJudgTiming { get; private set; } = new List<float>(); 29 /// <summary> 小節の数 </summary> 30 public float NumberOfMeasures { get; private set; } 31 /// <summary> 一小節に掛かる時間(秒) </summary> 32 public float OneBarTime { get; private set; } 33 /// <summary> 一拍に掛かる時間(秒) </summary> 34 public float OneBeatTime { get; private set; } 35 MusicDTO.EditData musicalData; 36 37 void Awake() 38 { 39 LoadNotesData(); 40 PropertyInitializer(); 41 SubstituteMusicalScoreData(); 42 } 43 44 /// <summary> 45 /// 譜面の小節数、一小節、一拍に掛かる時間を格納 46 /// </summary> 47 public void PropertyInitializer() 48 { 49 NumberOfMeasures = musicalData.BPM / 4f; 50 OneBarTime = 60f / NumberOfMeasures; 51 OneBeatTime = OneBarTime / 16f; 52 } 53 54 /// <summary> 55 /// 譜面からNotesの情報を取得して格納する 56 /// </summary> 57 public void LoadNotesData() 58 { 59 try 60 { 61 FileInfo file = new FileInfo(filePath); 62 using (StreamReader sr = new StreamReader(file.OpenRead())) 63 { 64 musicalData = JsonUtility.FromJson<MusicDTO.EditData>(sr.ReadToEnd()); 65 } 66 } 67 catch (FileNotFoundException e) 68 { 69 Debug.LogError(e + "指定されたファイルが見つかりませんでした\n"); 70 } 71 } 72 73 /// <summary> 74 /// 譜面データをプロパティに代入する 75 /// </summary> 76 public void SubstituteMusicalScoreData() 77 { 78 Name = musicalData.name; 79 MaxBlock = musicalData.maxBlock; 80 BPM = musicalData.BPM; 81 Offset = musicalData.offset; 82 foreach (var data in musicalData.notes) 83 { 84 LPB.Add(data.LPB); 85 Num.Add(data.num); 86 Block.Add(data.block); 87 NoteJudgTiming.Add(data.num * OneBeatTime); 88 } 89 Notes = musicalData.notes; 90 } 91 }

 追記
使用している譜面制作エディタはこちらになります。
NoteEditor

追記2
譜面データを変数に格納して管理するクラスがあり、
ノートの番号ごとに判定ラインに重なる時間を保持していて、その値をNoteクラスで使いたいのですが、
Noteクラスが一つ一つのノートにアタッチされているので、ノートごとにそのノートに紐づいた値を
取得することができませんでした。この場合管理の仕方が間違っているのでしょうか。
使いたい変数はNoteJudgTimingです。

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

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

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

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

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

ayousanz

2021/05/09 06:40

> タイミングよく判定ラインに降らせる事ができませんでした。 どのくらいずれているのか測定されましたか?誤差がどのくらいかわかるとある程度原因が特定できるのではないかと思います.
YDK

2021/05/09 14:27

返信ありがとうございます。 最初の方は0.3秒くらいだったんですけど徐々に差が開いて曲が始まって7秒くらいには1.5秒ぐらい 差が出てました。自分なりの方法で試してるので何かわかる事がありましたらまたお願いします。
ayousanz

2021/05/10 08:08 編集

原因かどうかわかりませんが,基本的にFixedUpdate()は使わずにUpdateを使ったほうがいいです. またObjectPoolについて詳しくないので,間違っているかもしれませんがObjectPoolを管理するScriptは存在しないのでしょうか??
YDK

2021/05/10 03:58

すみません。総菜の意味が調べてもわからなかったのですがどういう意味ですか? 相殺、または存在だったりしますか?
退会済みユーザー

退会済みユーザー

2021/05/10 06:33

考えられる原因ですが、 ①計算式に間違いがある。 ②float値による計算の誤差が秒数として表れている。 あたりが濃厚でしょう。 ズレ以外はうまくいっているなら②にしては誤差が大きすぎる気もするので、①ではなかろうか? ですが個人的に思うのは、floatで小節単位に分割して何度も計算を行うという方法だと曲の終わり頃にはそれなりの誤差が表れる気がするのであまり良くない気もします。
torisan3

2021/05/10 07:33

> noteSpeed = 4f * 158f / 60f; 158はBPMなのかなと思いましたが、これで合っていますか?
YDK

2021/05/10 07:54

158はBPMで合っています。 とりあえず実装したかったのでハードコーディングしてしまいました。 すみません。
ayousanz

2021/05/10 08:10

> すみません。総菜の意味が調べてもわからなかったのですがどういう意味ですか? 相殺、または存在だったりしますか? 変換ミスです. 総菜 -> 存在です(コメント修正しました)
tor4kichi

2021/05/10 08:38

NoteのnoteSpeedの単位ってなんでしょう。秒あたりの進む距離? ビートあたりの進む距離? それを変数名にも反映したり、全体に単位がちゃんと揃ってると単位のズレが認識しやすいと思います。 あとNoteCreaterのビート毎秒計算するところは float beatPerSecond = music.BPM / 60.0f; じゃないでしょうか。
YDK

2021/05/10 09:05

コードを修正しました。 使っている譜面制作エディタが一小節16分割されているのでその配置されたノートのnum番目×distance で生成する位置を決めているので合っていると思います。
退会済みユーザー

退会済みユーザー

2021/05/10 10:18

目を通して見た所、 AudioSourcesにint timeSamplesという変数が存在します。 難易度が高くコードは読めませんでしたが、時間に関する部分はこちらの値を基準に使われている様に思います。 という事はfloat型による演算誤差な気がしてきました。
YDK

2021/05/10 13:36

投稿ありがとうございます。 自分でもAudioSource.timeSamplesを使ってAudioSource.timeとどう違うのか出力したのですが、 timeSamplesの値が大きすぎてよく分かりませんでした。 色々なサイトを調べてみてるのですが、曲の再生位置を元にノートの位置が決まるらしいので、 おそらくAudioSource.timeを使った処理だと思うのですが、演算誤差の可能性もあるので自分なりに 調べたいと思います。
退会済みユーザー

退会済みユーザー

2021/05/10 18:53

ネットで見つけたもので参考になりそうな記事です。 AudioSources.timeSamples AudioSorce(音声ファイル)のサンプリングの再生値を返してくれます。 たとえばサンプリングレートが44.1MHzの音声ファイルを1秒再生したら、AudioSource.timeSamples の値は 44,100になります。 ここから少し音楽の知識も入ってきますが、一般的な曲のテンポとして1分間に120拍というスピードがあります。これをBPM(Beats Per Minute)=120と言います。 この時、1秒間に2拍(=120拍÷60秒)となるので、サンプリングレートが44.1MHzの音声ファイルで AudioSource.timeSamples の値が 44,100 ならば2拍進んだことになります。 あとは値に応じて処理すると、曲のビートに合わせて処理する動作になります。
YDK

2021/05/11 15:36

返信ありがとうございます。 値が大きかったのはただのHzだったからなんですね。 ということは音声ファイルの周波数を調べる必要があるってことですね。 自分で調べても見つけられなかったので助かりました。 1570pさんに教えていただいたtimeSamplesの方でノートのタイミング調整を行いたいと思います。 調べていただきありがとうございました。 また何かあったら質問させていただきます。
退会済みユーザー

退会済みユーザー

2021/05/20 07:20

Note側から処理したいという方法なのであれば、NotesCreaterからの生成時に MusicDTO.Note note = music.Notes[i]; この個別の参照をNoteクラス自身に渡して情報を持ってしまえばnote.num * OneBeatTimeで自身のタイミングの値は分かるのでは? 個別のクラスが管理するかマスタークラスが一括で管理するかって選択か?こういった部分は考え方の問題だと思います。 ここからは質問とは関係ない部分です。 同じ意味の情報(参照でなく)を色々なクラスが持つのは良くないと思います。 例えば、LoadManagerにList<float>なんちゃら、のようなプロパティが複数ありますが、それはList<MusicDTO.Notes> notesに格納されたものと同じなはず。廉価版の情報群みたいな事になってる気がする。 get;private set;プロパティにしてのアクセス制限も、 private List<MusicDTO.Notes> notes; public IReadOnlyList<MusicDTO.Notes> Notes => notes; とかにする感じな気がする。 public List<MusicDTO.Notes> notes {get; pprivate set; } だと、notesは変更できなくても、notes.LPB = 100;とか変更出来ます。 notes本体にアクセス制限はかかっていてもnotesの中身にアクセス制限はかかっていません。 あとIReadOnlyListも、キャストしてごにょごにょ出来るって話だった気がするので厳密にはもっと違う書き方だった気もする、忘れた。そういう話はなんとなく覚えてれば使う時に調べる事ができますね。
YDK

2021/05/20 15:08

回答ありがとうございます。 無事にノートの持つ判定のタイミングを取得することができました。 クラスの設計については今後対応していきたいと思います。
guest

回答1

0

自己解決

やりたかったことの一部は実装できたので回答を締め切りたいと思います。
協力してくださった皆さんありがとうございます。
今回1570pさんをベストアンサーにしたかったのですが、回答欄の方に書かれていなかったので、
自己解決という風な形になってしまいました。申し訳ございません。
まだやりたいことは残ってるので、もしまた分からないことがあれば質問するのでその時はよろしくお願いします。

投稿2021/05/20 15:15

YDK

総合スコア63

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問