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

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

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

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

JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Unity

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

Q&A

0回答

145閲覧

音ゲーのロングノーツ判定がうまくいかない

kaaak496

総合スコア8

C#

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

JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Unity

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

0グッド

0クリップ

投稿2024/11/18 13:18

実現したいこと

音ゲーのロングノーツの終点判定を実装したい

・ロングノーツが始まるタイミングでボタンの長押しをはじめ、そのまま終点を過ぎるまで押し続ければPerfectと表示させるようにしたい。

発生している問題・分からないこと

下記のJudge.csにおいて、ロングノーツを含んだ判定システムを作成した。
しかし、終点判定がうまくいかない。
下記のコードでは、最初の一回目のロングノーツでは期待どおりに動作しますが、それ以降のロングノーツではうまく動作しません。Debug.Logをしたところ、下記のようになり、
longNoteTest.jsの譜面を使用した際のログ
ロングノーツの始点の時間と終点の時間が等しくなってしまうことにあるようです。しかしそれを等しくさせない方法が分かりません。その方法さえ分かったしまえば解決するのではないかと考えています。
長いコードですがよろしくお願いします。

エラーメッセージ

error

1エラーメッセージは特にない。 2 3警告は出ている 4Serialization depth limit 10 exceeded at 'Note.notes'. There may be an object composition cycle in one or more of your serialized classes. 5 6Serialization hierarchy: 711: Note.notes 810: Note.notes 99: Note.notes 108: Note.notes 117: Note.notes 126: Note.notes 135: Note.notes 144: Note.notes 153: Note.notes 162: Note.notes 171: Note.notes 180: Data.notes 19 20UnityEngine.JsonUtility:FromJson<Data> (string) 21NotesManager:Load (string) (at Assets/Scripts/NotesManager.cs:59) 22NotesManager:OnEnable () (at Assets/Scripts/NotesManager.cs:53)

該当のソースコード

C#

1public class Judge : MonoBehaviour 2{ 3 [SerializeField] private GameObject[] MessageObj; 4 [SerializeField] private NotesManager notesManager; 5 [SerializeField] private AudioClip hitSound; 6 private AudioSource audio; 7 8 [SerializeField] private float _errorPerfect = 0.05f; 9 [SerializeField] private float _errorGreat = 0.13f; 10 [SerializeField] private float _errorBad = 0.2f; 11 12 private int currentIndex = 0; 13 14 private int[] longNotesIndex = new int[3]; 15 16 private Dictionary<int, bool> isHolding = new Dictionary<int, bool> { { 0, false }, { 1, false } }; 17 private Dictionary<int, float> endHoldTime = new Dictionary<int, float> { { 0, 0f }, { 1, 0f } }; 18 19 void Start() 20 { 21 audio = GetComponent<AudioSource>(); 22 longNotesIndex[0] = 0; 23 longNotesIndex[1] = 0; 24 longNotesIndex[2] = 0; 25 } 26 27 void Update() 28 { 29 if (GameManager.instance.Start) 30 { 31 HandleInput(KeyCode.D, 0); 32 HandleInput(KeyCode.E, 1); 33 HandleMissNotes(); 34 } 35 } 36 37 void HandleInput(KeyCode key, int lane) 38 { 39 if (Input.GetKeyDown(key)) 40 ProcessKeyPress(lane); 41 42 if (isHolding[lane] && Input.GetKey(key)) 43 CheckLongNoteHold(lane); 44 else if (isHolding[lane] && !Input.GetKey(key)) 45 EndLongNote(lane, false); 46 } 47 48 void ProcessKeyPress(int lane) 49 { 50 for (int i = currentIndex; i < notesManager.LaneNum.Count; i++) 51 { 52 if (notesManager.LaneNum[i] == lane) 53 { 54 float noteTime = notesManager.NotesTime[i] + GameManager.instance.StartTime; 55 float timeLag = Mathf.Abs(Time.time - noteTime); 56 57 if (notesManager.NoteType[i] == 1) // 通常ノート 58 { 59 EvaluateJudgement(timeLag, i,false); 60 //longNotesIndex[lane]++; 61 Debug.Log("longNoteIndex["+lane+"] = "+ longNotesIndex[lane]); 62 } 63 else if (notesManager.NoteType[i] == 2) // ロングノート 64 { 65 if (EvaluateJudgement(timeLag, i,true)) 66 { 67 isHolding[lane] = true; 68 Debug.Log("start holding time="+Time.time); 69 endHoldTime[lane] = CalculateEndHoldTime(i, lane); 70 //longNotesIndex[lane]++; 71 Debug.Log("longNoteIndex[" + lane + "] = " + longNotesIndex[lane]); 72 } 73 else 74 { 75 //longNotesIndex[lane]++; 76 Debug.Log("longNoteIndex[" + lane + "] = " + longNotesIndex[lane]); 77 } 78 } 79 break; 80 } 81 } 82 } 83 84 void CheckLongNoteHold(int lane) 85 { 86 if (Time.time >= endHoldTime[lane]) 87 { 88 Debug.Log("end hold time="+Time.time); 89 EndLongNote(lane, true); 90 91 } 92 93 if (isHolding[lane]) 94 { 95 //Debug.Log("now holding"); 96 } 97 } 98 99 void EndLongNote(int lane, bool success) 100 { 101 isHolding[lane] = false; 102 DisplayMessage(success ? 0 : 3, lane); // 0: Perfect, 3: Miss 103 } 104 105 void HandleMissNotes() 106 { 107 while (currentIndex < notesManager.LaneNum.Count) 108 { 109 float noteTime = notesManager.NotesTime[currentIndex] + GameManager.instance.StartTime; 110 if (Time.time > noteTime + _errorBad) 111 { 112 longNotesIndex[2]++; 113 Debug.Log("longNoteIndex[2] = " + longNotesIndex[2]); 114 DisplayMessage(3, notesManager.LaneNum[currentIndex]); // 3: Miss 115 RemoveNoteAt(currentIndex); 116 } 117 else 118 { 119 break; 120 } 121 } 122 } 123 124 bool EvaluateJudgement(float timeLag, int noteIndex,bool usedForBool) 125 { 126 int lane = notesManager.LaneNum[noteIndex]; 127 if (timeLag <= _errorPerfect) 128 { 129 if (!usedForBool) { longNotesIndex[lane]++; } 130 DisplayMessage(0, lane); // Perfect 131 } 132 else if (timeLag <= _errorGreat) 133 { 134 if (!usedForBool) { longNotesIndex[lane]++; } 135 DisplayMessage(1, lane); // Great 136 } 137 else if (timeLag <= _errorBad) 138 { 139 if (!usedForBool) { longNotesIndex[lane]++; } 140 DisplayMessage(2, lane); // Bad 141 } 142 else 143 { 144 return false; 145 } 146 147 RemoveNoteAt(noteIndex); 148 return true; 149 } 150 151 float CalculateEndHoldTime(int startNoteIndex,int lane) 152 { 153 if(GetLastChildNoteOf(startNoteIndex, lane) != null) 154 { 155 var lastChildNote = GetLastChildNoteOf(startNoteIndex, lane); 156 RemoveNoteAt(startNoteIndex); 157 float beatSec = 60f / notesManager.inputJson.BPM; 158 return (beatSec * lastChildNote.num / (float)lastChildNote.LPB) + GameManager.instance.StartTime; 159 } 160 else 161 { 162 return 0; 163 } 164 165 } 166 167 void DisplayMessage(int judge, int lane) 168 { 169 Instantiate(MessageObj[judge], new UnityEngine.Vector3(0.76f, lane * 5 - 3f, 0), UnityEngine.Quaternion.Euler(45, 0, 0)); 170 if (judge < 3) audio.PlayOneShot(hitSound); // Play sound for Perfect, Great, Bad 171 } 172 173 public void RemoveNoteAt(int index) 174 { 175 if (index >= 0 && index < notesManager.NotesTime.Count) 176 { 177 notesManager.NotesTime.RemoveAt(index); 178 notesManager.LaneNum.RemoveAt(index); 179 notesManager.NoteType.RemoveAt(index); 180 } 181 } 182 183 public Note GetLastChildNoteOf(int startNoteIndex,int lane) 184 { 185 186 Note[] childNotes = notesManager.inputJson.notes[startNoteIndex].notes; 187 Debug.Log("ChildNotes.Length"+childNotes.Length); 188 if (childNotes != null && childNotes.Length > 0) 189 { 190 return childNotes[0]; // 最後の子ノートを返す 191 } 192 else 193 { 194 return null; 195 } 196 197 } 198 199 200} 201

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

①下記の動画シリーズを参考にノーマルノーツの判定システムまで作成した。
https://youtu.be/WWeyn4TI0lI?si=bDRfq8deHDgJaJXE

②ロングノーツを実装しようと思い、chatGPTを使用しながら実装しようとした。
ロジックとしては、ロングノーツの始点のデータのnotes[]の直下にあるnotes[]の中のnumという要素を利用して計算を行うことでロングノーツの終点の時間を計算した。
NotesManagerの下記の計算方法を応用して

csharp

1kankaku[i] = 60 / (inputJson.BPM * (float)inputJson.notes[i].LPB); 2beatSec[i] = kankaku[i] * (float)inputJson.notes[i].LPB; 3time[i] = (beatSec[i] * inputJson.notes[i].num / (float)inputJson.notes[i].LPB) + inputJson.offset * 0.01f; 4NotesTime.Add(time[i]); 5LaneNum.Add(inputJson.notes[i].block); 6NoteType.Add(inputJson.notes[i].type);

Judge.csで下記のような計算にした

csharp

1float CalculateEndHoldTime(int startNoteIndex,int lane) 2{ 3 if(GetLastChildNoteOf(startNoteIndex, lane) != null) 4 { 5 var lastChildNote = GetLastChildNoteOf(startNoteIndex, lane); 6 //RemoveNoteAt(startNoteIndex); 7 float beatSec = 60f / notesManager.inputJson.BPM; 8 return (beatSec * lastChildNote.num / (float)lastChildNote.LPB) + GameManager.instance.StartTime; 9 } 10 else 11 { 12 return 0; 13 } 14 15}

しかし、質問に投稿したような問題が依然起こってしまう。

ブレイクポイントやウォッチを使用して、リストに格納されたデータも見ていましたが、2回目以降のロングノーツでは見れないので断念しました。

chsharp

1public Note GetLastChildNoteOf(int startNoteIndex,int lane) 2{ 3 4 Note[] childNotes = notesManager.inputJson.notes[longNotesIndex[0] + longNotesIndex[1] + longNotesIndex[2]].notes; 5 Debug.Log("ChildNotes.Length"+childNotes.Length); 6 if (childNotes != null && childNotes.Length > 0) 7 { 8 return childNotes[0]; // 最後の子ノートを返す 9 } 10 else 11 { 12 return null; 13 } 14 15}

上記のようにlongNoteIndexの各要素の総和を求めればうまくいくかもしれないと考えたが、改善せず。

かれこれ半日から1日考えていますが、依然として解決せず、埒が明かないため相談しました。

補足

ツール: Unity 2022.3.3.15 Visual Studio 2022
譜面作成ツール:NoteEditor NoteEditor - github

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

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

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

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

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

kaaak496

2024/11/18 13:18

NotesManager.cs ```csharp using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.EventSystems; [Serializable] public class Data { public string name; public int maxBlock; public int BPM; public int offset; public Note[] notes; } [Serializable] public class Note { public int type; public int num; public int block; public int LPB; public Note[] notes; } public class NotesManager : MonoBehaviour { public int noteNum; private string songName; public List<int> LaneNum = new List<int>(); public List<int> NoteType = new List<int>(); public List<float> NotesTime = new List<float>(); public List<GameObject> NotesObj = new List<GameObject>(); private float NotesSpeed; public Data inputJson; [SerializeField] GameObject noteObj; [SerializeField] GameObject longNoteStartObj; [SerializeField] GameObject longNoteMidObj; [SerializeField] GameObject longNoteEndObj; [SerializeField] GameObject detectingLine; void OnEnable() { NotesSpeed = GameManager.instance.noteSpeed; noteNum = 0; songName = "longNoteTest"; Load(songName); } private void Load(string SongName) { string inputString = Resources.Load<TextAsset>(SongName).ToString(); inputJson = JsonUtility.FromJson<Data>(inputString); noteNum = inputJson.notes.Length; float[] kankaku = new float[noteNum]; float[] beatSec = new float[noteNum]; float[] time = new float[noteNum]; float[] x = new float[noteNum]; for (int i = 0; i < inputJson.notes.Length; i++) { kankaku[i] = 60 / (inputJson.BPM * (float)inputJson.notes[i].LPB); beatSec[i] = kankaku[i] * (float)inputJson.notes[i].LPB; time[i] = (beatSec[i] * inputJson.notes[i].num / (float)inputJson.notes[i].LPB) + inputJson.offset * 0.01f; NotesTime.Add(time[i]); LaneNum.Add(inputJson.notes[i].block); NoteType.Add(inputJson.notes[i].type); x[i] = NotesTime[i] * NotesSpeed; } for (int i = 0; i < inputJson.notes.Length; i++) { if (NoteType[i] == 2) // ロングノーツの場合 { float longStartTime = NotesTime[i]; // ロングノート終了時点を計算 float longEndTime = longStartTime; if (inputJson.notes[i].notes.Length > 0) { // 子ノートの最後のタイミングを基準に終了時点を計算 var lastChildNote = inputJson.notes[i].notes[inputJson.notes[i].notes.Length - 1]; longEndTime = (60f / (inputJson.BPM * lastChildNote.LPB)) * lastChildNote.num + inputJson.offset * 0.01f; } float longDuration = longEndTime - longStartTime; if (longDuration <= 0) { Debug.LogWarning($"Invalid long note duration at index {i}"); continue; // 異常値の場合スキップ } // ロングノートの始点 NotesObj.Add(Instantiate(longNoteStartObj, new Vector3(x[i] + detectingLine.transform.position.x, inputJson.notes[i].block * 5 - 3f, 0), longNoteStartObj.transform.rotation)); // 間のオブジェクトの配置 float interval = 1f / NotesSpeed; // 生成間隔 for (float t = interval; t < longDuration; t += interval) { float midZ = (longStartTime + t) * NotesSpeed; if (t + interval >= longDuration) { // 終点オブジェクト NotesObj.Add(Instantiate(longNoteEndObj, new Vector3(midZ + detectingLine.transform.position.x, inputJson.notes[i].block * 5 - 3f, 0), longNoteEndObj.transform.rotation)); } else { // 間オブジェクト NotesObj.Add(Instantiate(longNoteMidObj, new Vector3(midZ + detectingLine.transform.position.x, inputJson.notes[i].block * 5 - 3f, 0), Quaternion.identity)); } } } else if (NoteType[i] == 1) // 通常ノートの場合 { NotesObj.Add(Instantiate(noteObj, new Vector3(x[i] + detectingLine.transform.position.x, inputJson.notes[i].block * 5 - 3f, 0), Quaternion.identity)); } } } } ```
kaaak496

2024/11/18 13:21

Jsonの形式 ```Json { "name": "longNoteTest", "maxBlock": 2, "BPM": 70, "offset": 0, "notes": [ { "LPB": 4, "num": 4, "block": 0, "type": 2, "notes": [ { "LPB": 4, "num": 8, "block": 0, "type": 2, "notes": [] } ] }, { "LPB": 4, "num": 12, "block": 0, "type": 1, "notes": [] }, { "LPB": 4, "num": 16, "block": 1, "type": 2, "notes": [ { "LPB": 4, "num": 20, "block": 1, "type": 2, "notes": [] } ] }, { "LPB": 4, "num": 24, "block": 1, "type": 1, "notes": [] }, { "LPB": 4, "num": 28, "block": 0, "type": 2, "notes": [ { "LPB": 4, "num": 36, "block": 0, "type": 2, "notes": [] } ] }, { "LPB": 4, "num": 40, "block": 0, "type": 1, "notes": [] }, { "LPB": 4, "num": 44, "block": 1, "type": 2, "notes": [ { "LPB": 4, "num": 52, "block": 1, "type": 2, "notes": [] } ] }, { "LPB": 4, "num": 56, "block": 1, "type": 1, "notes": [] }, { "LPB": 4, "num": 60, "block": 0, "type": 2, "notes": [ { "LPB": 4, "num": 68, "block": 0, "type": 2, "notes": [] } ] }, ``` ロングノーツは、 { "LPB": 4, "num": 60, "block": 0, "type": 2, "notes": [ { "LPB": 4, "num": 68, "block": 0, "type": 2, "notes": [] } ] }, これでひとまとまりで階層構造で子のnotesの中身に終点にあたるデータが入っています。
kaaak496

2024/11/18 17:12

Unityのシリアライザのせいかもしれません。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだ回答がついていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.37%

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

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

質問する

関連した質問