実現したいこと
音ゲーのロングノーツの終点判定を実装したい
・ロングノーツが始まるタイミングでボタンの長押しをはじめ、そのまま終点を過ぎるまで押し続ければPerfectと表示させるようにしたい。
発生している問題・分からないこと
下記のJudge.csにおいて、ロングノーツを含んだ判定システムを作成した。
しかし、終点判定がうまくいかない。
下記のコードでは、最初の一回目のロングノーツでは期待どおりに動作しますが、それ以降のロングノーツではうまく動作しません。Debug.Logをしたところ、下記のようになり、
ロングノーツの始点の時間と終点の時間が等しくなってしまうことにあるようです。しかしそれを等しくさせない方法が分かりません。その方法さえ分かったしまえば解決するのではないかと考えています。
長いコードですがよろしくお願いします。
エラーメッセージ
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
あなたの回答
tips
プレビュー