From 373f0511e34ae33ac740dc8c8c0f8f234a158494 Mon Sep 17 00:00:00 2001 From: Jiangqvweihuan <15987148+jiangqvweihuan@user.noreply.gitee.com> Date: Sun, 20 Jul 2025 03:07:20 +0000 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=83=A8=E5=88=86=E7=9A=84?= =?UTF-8?q?=E9=95=BF=E9=9F=B3=E7=AC=A6=E5=88=A4=E5=AE=9Abug=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=A4=9A=E9=94=AE=E4=BD=8D=E5=90=8C=E6=97=B6?= =?UTF-8?q?=E8=A7=A6=E5=8F=91=E7=9A=84=E5=88=A4=E5=AE=9A=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jiangqvweihuan <> --- .../scripts/gamePlay_gameplay/InputManager.cs | 52 ++++ .../gamePlay_gameplay/InputManager.cs.meta | 11 + .../scripts/gamePlay_gameplay/JudgeManager.cs | 196 ++++++++++++++ .../gamePlay_gameplay/JudgeManager.cs.meta | 11 + .../gamePlay_gameplay/KeyBindingManager.cs | 74 ++++++ .../KeyBindingManager.cs.meta | 11 + .../gamePlay_gameplay/NewBehaviourScript.cs | 87 ++++++ .../NewBehaviourScript.cs.meta | 11 + Assets/scripts/gamePlay_gameplay/Note.cs | 133 ++++++++++ Assets/scripts/gamePlay_gameplay/Note.cs.meta | 11 + Assets/scripts/gamePlay_gameplay/NotePool.cs | 196 ++++++++++++++ .../gamePlay_gameplay/NotePool.cs.meta | 11 + .../scripts/gamePlay_gameplay/NoteSpawner.cs | 247 ++++++++++++++++++ .../gamePlay_gameplay/NoteSpawner.cs.meta | 11 + 14 files changed, 1062 insertions(+) create mode 100644 Assets/scripts/gamePlay_gameplay/InputManager.cs create mode 100644 Assets/scripts/gamePlay_gameplay/InputManager.cs.meta create mode 100644 Assets/scripts/gamePlay_gameplay/JudgeManager.cs create mode 100644 Assets/scripts/gamePlay_gameplay/JudgeManager.cs.meta create mode 100644 Assets/scripts/gamePlay_gameplay/KeyBindingManager.cs create mode 100644 Assets/scripts/gamePlay_gameplay/KeyBindingManager.cs.meta create mode 100644 Assets/scripts/gamePlay_gameplay/NewBehaviourScript.cs create mode 100644 Assets/scripts/gamePlay_gameplay/NewBehaviourScript.cs.meta create mode 100644 Assets/scripts/gamePlay_gameplay/Note.cs create mode 100644 Assets/scripts/gamePlay_gameplay/Note.cs.meta create mode 100644 Assets/scripts/gamePlay_gameplay/NotePool.cs create mode 100644 Assets/scripts/gamePlay_gameplay/NotePool.cs.meta create mode 100644 Assets/scripts/gamePlay_gameplay/NoteSpawner.cs create mode 100644 Assets/scripts/gamePlay_gameplay/NoteSpawner.cs.meta diff --git a/Assets/scripts/gamePlay_gameplay/InputManager.cs b/Assets/scripts/gamePlay_gameplay/InputManager.cs new file mode 100644 index 00000000..0174741e --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/InputManager.cs @@ -0,0 +1,52 @@ +using UnityEngine; +using System; + +public class InputManager : MonoBehaviour +{ + public static event Action OnKeyPressed; + public static event Action OnKeyReleased; + + // 存储当前帧所有按下的按键 + private KeyCode[] pressedKeysThisFrame = new KeyCode[5]; + private int pressedKeyCount = 0; + + private void Update() + { + pressedKeyCount = 0; + + // 检查所有可能的按键 + foreach (string color in new string[] { "red", "green", "yellow", "purple", "blue" }) + { + KeyCode key = KeyBindingManager.GetKeyForColor(color); + if (key == KeyCode.None) continue; + + if (Input.GetKeyDown(key)) + { + // 记录当前帧按下的所有按键 + pressedKeysThisFrame[pressedKeyCount++] = key; + OnKeyPressed?.Invoke(key); + } + if (Input.GetKeyUp(key)) + { + OnKeyReleased?.Invoke(key); + } + } + + // 处理多键同时按下 + if (pressedKeyCount > 1) + { + HandleMultiKeyPress(); + } + } + + private void HandleMultiKeyPress() + { + // 对当前帧所有按下的按键进行处理 + for (int i = 0; i < pressedKeyCount; i++) + { + KeyCode key = pressedKeysThisFrame[i]; + // 调用 JudgeManager 统一判断对应按键的音符 + JudgeManager.Instance.JudgeEarliestNote(key); + } + } +} \ No newline at end of file diff --git a/Assets/scripts/gamePlay_gameplay/InputManager.cs.meta b/Assets/scripts/gamePlay_gameplay/InputManager.cs.meta new file mode 100644 index 00000000..657d2c78 --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/InputManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b2c4f957042317641bf529c141c39b91 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/scripts/gamePlay_gameplay/JudgeManager.cs b/Assets/scripts/gamePlay_gameplay/JudgeManager.cs new file mode 100644 index 00000000..03a81bf8 --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/JudgeManager.cs @@ -0,0 +1,196 @@ +using System.Collections.Generic; +using UnityEngine; + +public class JudgeManager : MonoBehaviour +{ + public static JudgeManager Instance { get; private set; } + + private Dictionary> judgeQueues = new Dictionary>(); + private Dictionary startJudgedNotes = new Dictionary(); + private Dictionary releasedNotes = new Dictionary(); + private Dictionary noteEndTimes = new Dictionary(); + private Dictionary _holdNoteStartTimes = new Dictionary(); + + // 闀块煶绗︾姸鎬佺鐞 + private Dictionary _holdNoteValidity = new Dictionary(); + private Dictionary _holdNoteKeyHeld = new Dictionary(); // 鏂板锛氳褰曟寜閿寜浣忕姸鎬 + private Dictionary> multiKeyJudgeQueues = new Dictionary>(); + + + + public void RegisterNoteForMultiKey(KeyCode key, Note note) + { + if (!multiKeyJudgeQueues.ContainsKey(key)) + multiKeyJudgeQueues[key] = new List(); + + if (!multiKeyJudgeQueues[key].Contains(note)) + { + multiKeyJudgeQueues[key].Add(note); + } + } + + public void JudgeAllNotesForMultiKey(KeyCode key) + { + if (multiKeyJudgeQueues.ContainsKey(key)) + { + // 瀵瑰悓涓鎸夐敭鐨勬墍鏈夐煶绗﹁繘琛屽垽瀹 + foreach (var note in multiKeyJudgeQueues[key]) + { + if (note != null && !note.IsJudged()) + { + note.Judge(); + } + } + multiKeyJudgeQueues[key].Clear(); + } + } + + private void Awake() + { + if (Instance == null) + { + Instance = this; + } + else + { + Destroy(gameObject); + return; + } + } + + #region 闀块煶绗︾姸鎬佺鐞 + public bool IsHoldNoteValid(int holdNoteId) + { + return _holdNoteValidity.ContainsKey(holdNoteId) && _holdNoteValidity[holdNoteId]; + } + + public void SetHoldNoteValidity(int holdNoteId, bool isValid) + { + _holdNoteValidity[holdNoteId] = isValid; + //Debug.Log($"璁剧疆闀块煶绗︽湁鏁堟: ID={holdNoteId}, Valid={isValid}"); + } + + public void RemoveHoldNoteValidity(int holdNoteId) + { + if (_holdNoteValidity.ContainsKey(holdNoteId)) + { + _holdNoteValidity.Remove(holdNoteId); + } + } + + // 鏂板锛氭敞鍐/鏇存柊鎸夐敭鎸変綇鐘舵 + public void RegisterHoldNoteKeyHeld(int holdNoteId, bool isHeld) + { + _holdNoteKeyHeld[holdNoteId] = isHeld; + } + + // 鏂板锛氭鏌ユ寜閿槸鍚︽寜浣 + public bool IsHoldNoteKeyHeld(int holdNoteId) + { + return _holdNoteKeyHeld.ContainsKey(holdNoteId) && _holdNoteKeyHeld[holdNoteId]; + } + #endregion + + #region 闀块煶绗︽椂闂磋褰 + public void RegisterScheduledEndTime(string noteId, float endTime) + { + noteEndTimes[noteId] = endTime; + } + + public float GetScheduledEndTime(string noteId) + { + return noteEndTimes.ContainsKey(noteId) ? noteEndTimes[noteId] : 0f; + } + + public void RegisterHoldNoteStart(string noteId, float startTime) + { + _holdNoteStartTimes[noteId] = startTime; + //Debug.Log($"娉ㄥ唽闀块煶绗﹀紑濮: ID={noteId}, Time={startTime}"); + } + + public bool IsHoldNoteActive(string noteId) + { + return _holdNoteStartTimes.ContainsKey(noteId) && + (Time.time - _holdNoteStartTimes[noteId]) < 5f; + } + #endregion + + #region 闊崇鍒ゅ畾鐘舵 + public void RegisterStartJudged(string noteID, bool state) + { + startJudgedNotes[noteID] = state; + //Debug.Log($"[JudgeManager] RegisterStartJudged: {noteID} = {state}"); + } + + public bool IsStartJudged(string noteID) + { + return startJudgedNotes.ContainsKey(noteID) && startJudgedNotes[noteID]; + } + + public void RegisterNoteReleased(string noteID, bool state) + { + releasedNotes[noteID] = state; + //Debug.Log($"[JudgeManager] RegisterNoteReleased: {noteID} = {state}"); + } + + public bool HasNoteReleased(string noteID) + { + return releasedNotes.ContainsKey(noteID) && releasedNotes[noteID]; + } + #endregion + + #region 鐭煶绗﹀垽瀹 + public void RegisterNote(KeyCode key, Note note) + { + if (!judgeQueues.ContainsKey(key)) + judgeQueues[key] = new Queue(); + + if (!judgeQueues[key].Contains(note)) + { + judgeQueues[key].Enqueue(note); + } + } + + public void UnregisterNote(KeyCode key, Note note) + { + if (!judgeQueues.ContainsKey(key)) return; + + Queue newQueue = new Queue(); + while (judgeQueues[key].Count > 0) + { + Note n = judgeQueues[key].Dequeue(); + if (n != note) + { + newQueue.Enqueue(n); + } + else if (!n.IsJudged()) + { + n.JudgeMiss(); + } + } + judgeQueues[key] = newQueue; + } + + public void JudgeEarliestNote(KeyCode key) + { + if (judgeQueues.ContainsKey(key) && judgeQueues[key].Count > 0) + { + Note note = judgeQueues[key].Dequeue(); + if (note != null && !note.IsJudged()) + { + note.Judge(); + } + } + } + #endregion + + #region 閲嶇疆绠$悊 + public void ResetAllHoldNotes() + { + _holdNoteValidity.Clear(); + _holdNoteStartTimes.Clear(); + _holdNoteKeyHeld.Clear(); + //Debug.Log("閲嶇疆鎵鏈夐暱闊崇鐘舵"); + } + #endregion +} diff --git a/Assets/scripts/gamePlay_gameplay/JudgeManager.cs.meta b/Assets/scripts/gamePlay_gameplay/JudgeManager.cs.meta new file mode 100644 index 00000000..ee590462 --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/JudgeManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e726455158177b3499eb8f0910c54e30 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/scripts/gamePlay_gameplay/KeyBindingManager.cs b/Assets/scripts/gamePlay_gameplay/KeyBindingManager.cs new file mode 100644 index 00000000..9630db24 --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/KeyBindingManager.cs @@ -0,0 +1,74 @@ +using UnityEngine; +using System.Collections.Generic; + +public class KeyBindingManager : MonoBehaviour +{ + private static Dictionary keyBindings = new Dictionary(); + + private void Awake() + { + //Debug.Log("KeyBindingManager Awake() 琚皟鐢紝寮濮嬪姞杞芥寜閿槧灏..."); + + if (keyBindings.Count == 0) + { + LoadKeyBindings(); + } + } + + /// 鑾峰彇棰滆壊瀵瑰簲鐨勬寜閿 + public static KeyCode GetKeyForColor(string color) + { + if (keyBindings.TryGetValue(color.ToLower(), out KeyCode key)) + { + return key; + } + //Debug.LogError($"鏈壘鍒伴鑹 {color} 瀵瑰簲鐨勬寜閿紒璇锋鏌 KeyBindingManager 鏄惁姝g‘鍒濆鍖栥"); + return KeyCode.None; + } + + /// 淇敼鎸夐敭缁戝畾 + public static void ChangeKeyBinding(string color, KeyCode newKey) + { + if (keyBindings.ContainsKey(color.ToLower())) + { + keyBindings[color.ToLower()] = newKey; + } + else + { + keyBindings.Add(color.ToLower(), newKey); + } + + SaveKeyBindings(); + } + + /// 瀛樺偍鎸夐敭缁戝畾鍒 `PlayerPrefs` + private static void SaveKeyBindings() + { + foreach (var kvp in keyBindings) + { + PlayerPrefs.SetInt($"KeyBinding_{kvp.Key}", (int)kvp.Value); + } + PlayerPrefs.Save(); + } + + /// 浠 `PlayerPrefs` 鍔犺浇鎸夐敭缁戝畾 + private static void LoadKeyBindings() + { + string[] colors = { "red", "green", "yellow", "purple", "blue" }; + KeyCode[] defaultKeys = { KeyCode.D, KeyCode.F, KeyCode.Space, KeyCode.J, KeyCode.K }; + + for (int i = 0; i < colors.Length; i++) + { + if (PlayerPrefs.HasKey($"KeyBinding_{colors[i]}")) + { + keyBindings[colors[i]] = (KeyCode)PlayerPrefs.GetInt($"KeyBinding_{colors[i]}"); + } + else + { + keyBindings[colors[i]] = defaultKeys[i]; + } + } + + //Debug.Log("KeyBindings 鍒濆鍖栨垚鍔燂細" + string.Join(", ", keyBindings)); + } +} diff --git a/Assets/scripts/gamePlay_gameplay/KeyBindingManager.cs.meta b/Assets/scripts/gamePlay_gameplay/KeyBindingManager.cs.meta new file mode 100644 index 00000000..3229b8f2 --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/KeyBindingManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2639b2e07c4995842bdbe955d9fb6bfb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/scripts/gamePlay_gameplay/NewBehaviourScript.cs b/Assets/scripts/gamePlay_gameplay/NewBehaviourScript.cs new file mode 100644 index 00000000..7f8d93d6 --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/NewBehaviourScript.cs @@ -0,0 +1,87 @@ +using UnityEngine; + +public class Notes : MonoBehaviour +{ + public int trackIndex; // 轨道索引 + public float hitTime; // 该音符应该被击中的时间 + private bool canBeJudged = false; // 是否进入判定区域 + private bool isHit = false; // 是否已被击中 + + private void Update() + { + // 过了 Miss 判定时间,销毁音符 + if (!isHit && Time.timeSinceLevelLoad > hitTime + 0.3f) + { + JudgeMiss(); + } + } + + private void OnTriggerEnter2D(Collider2D other) + { + if (other.CompareTag("JudgmentLine")) + { + canBeJudged = true; + } + } + + private void OnTriggerExit2D(Collider2D other) + { + if (other.CompareTag("JudgmentLine")) + { + canBeJudged = false; + } + } + + public void JudgeNote() + { + if (!canBeJudged || isHit) return; // 仅在进入判定区后才能被判定 + + float currentTime = Time.timeSinceLevelLoad; + float timeDifference = Mathf.Abs(currentTime - hitTime); + + if (timeDifference <= 0.05f) + { + JudgePerfect(); + } + else if (timeDifference <= 0.1f) + { + JudgeGreat(); + } + else if (timeDifference <= 0.2f) + { + JudgeGood(); + } + else if (timeDifference <= 0.3f) + { + JudgeMiss(); + } + } + + private void JudgePerfect() + { + //Debug.Log($"Track {trackIndex}: Perfect!"); + isHit = true; + Destroy(gameObject); + } + + private void JudgeGreat() + { + //Debug.Log($"Track {trackIndex}: Great!"); + isHit = true; + Destroy(gameObject); + } + + private void JudgeGood() + { + //Debug.Log($"Track {trackIndex}: Good!"); + isHit = true; + Destroy(gameObject); + } + + private void JudgeMiss() + { + //Debug.Log($"Track {trackIndex}: Miss!"); + isHit = true; + Destroy(gameObject); + } +} diff --git a/Assets/scripts/gamePlay_gameplay/NewBehaviourScript.cs.meta b/Assets/scripts/gamePlay_gameplay/NewBehaviourScript.cs.meta new file mode 100644 index 00000000..16e7eb0e --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/NewBehaviourScript.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d8062cc45ed8f84da8460fcd2964b77 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/scripts/gamePlay_gameplay/Note.cs b/Assets/scripts/gamePlay_gameplay/Note.cs new file mode 100644 index 00000000..5e1e4b9c --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/Note.cs @@ -0,0 +1,133 @@ +using UnityEngine; + +public class Note : BaseNote +{ + private KeyCode keyToPress; + private string noteColor; + private AnimationController anim; + private NoteController controller; + private bool isJudged = false; + + private void Awake() + { + anim = GetComponent(); + controller = GetComponent(); + } + + public void Setup(KeyCode key, int trackIndex, float speed, float hitTime, string color) + { + keyToPress = key; + noteColor = color; + TrackIndex = trackIndex; + Speed = speed; + this.hitTime = hitTime; + isJudged = false; + + if (controller != null) + { + controller.SetSpeed(speed); + } + + // 同时注册到普通队列和多键队列 + InputManager.OnKeyPressed += HandlePress; + JudgeManager.Instance.RegisterNoteForMultiKey(key, this); + } + + private void OnDestroy() + { + InputManager.OnKeyPressed -= HandlePress; + JudgeManager.Instance?.UnregisterNote(keyToPress, this); + } + + private void HandlePress(KeyCode key) + { + // 确保只有匹配的按键触发,并且音符处于判定区域 + if (isJudged || key != keyToPress || (controller != null && !controller.IsInJudgeZone())) + return; + + float timeDifference = Mathf.Abs(Time.time - hitTime); + + if (timeDifference <= 0.08f) + { + //Debug.Log($"短音符 {keyToPress}: Perfect"); + } + else if (timeDifference <= 0.15f) + { + //Debug.Log($"短音符 {keyToPress}: Great"); + } + else + { + //Debug.Log($"短音符 {keyToPress} Miss"); + JudgeMiss(); + return; + } + + Judge(); + } + + public bool IsJudged() + { + return isJudged; + } + + public void Judge() + { + if (isJudged) + return; + isJudged = true; + + if (controller != null) + { + controller.StopMovement(); + controller.PlayHitEffect(); + } + ReturnToPool(); + if (anim !=null) + { + //Debug.Log("执行"); + anim.PlayDestroyAnimation(noteColor); + } + + } + + public void JudgeMiss() + { + if (isJudged) return; + isJudged = true; + //Debug.Log($"短音符 {keyToPress} Miss"); + ReturnToPool(); + } + + private void ReturnToPool() + { + InputManager.OnKeyPressed -= HandlePress; + if (controller != null) + { + controller.ResetState(); + } + gameObject.SetActive(false); + NotePool.Instance.ReturnNote(gameObject, noteColor); + } + + public int GetTrackIndex() + { + return TrackIndex; + } + + public KeyCode GetKey() + { + return keyToPress; + } + + /// + /// 由 Controller 调用,通知 Note 是否在判定区域内 + /// + public void SetJudgeZone(bool inZone) + { + // 如果音符离开判定区域且还未判定,则判为 Miss + if (!inZone && !isJudged) + { + JudgeMiss(); + } + } +} diff --git a/Assets/scripts/gamePlay_gameplay/Note.cs.meta b/Assets/scripts/gamePlay_gameplay/Note.cs.meta new file mode 100644 index 00000000..c72e2d73 --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/Note.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a7d5a4ca6fc6b784d89e910ff90c2c7a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/scripts/gamePlay_gameplay/NotePool.cs b/Assets/scripts/gamePlay_gameplay/NotePool.cs new file mode 100644 index 00000000..f0d486df --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/NotePool.cs @@ -0,0 +1,196 @@ +using System.Collections.Generic; +using UnityEngine; + +public class NotePool : MonoBehaviour +{ + public static NotePool Instance { get; private set; } + + public GameObject[] notePrefabs; // 短音符预制体(按颜色存储) + public GameObject[] holdNotePrefabs; // 长音符片段预制体 + public GameObject[] startNotePrefabs; // 长音符start片段预制体 + private int poolSize = 12; // 每种类型的音符池大小 + private int maxPoolSize = 60; // 池的最大容量 + + private Dictionary> notePools; // 短音符对象池 + private Dictionary> holdNotePools; // 长音符片段对象池 + private Dictionary> startNotePools; // 长音符start片段对象池 + + private void Awake() + { + if (Instance == null) + { + Instance = this; + } + else + { + Destroy(gameObject); + return; + } + + notePools = new Dictionary>(); + holdNotePools = new Dictionary>(); + startNotePools = new Dictionary>(); + + for (int i = 0; i < notePrefabs.Length; i++) + { + notePools[i] = new Stack(); + holdNotePools[i] = new Stack(); + startNotePools[i] = new Stack(); + + for (int j = 0; j < poolSize; j++) + { + AddToPool(notePools[i], notePrefabs[i]); + AddToPool(startNotePools[i], startNotePrefabs[i]); + } + + for (int j = 0; j < poolSize * 5; j++) + { + AddToPool(holdNotePools[i], holdNotePrefabs[i]); + } + } + } + + private GameObject GetObjectFromPool(Stack pool, GameObject prefab) + { + if (pool.Count > 0) + { + GameObject obj = pool.Pop(); + obj.SetActive(true); + return obj; + } + else + { + if (pool.Count < maxPoolSize) + { + GameObject obj = Instantiate(prefab); + obj.SetActive(true); + return obj; + } + else + { + //Debug.LogWarning("对象池已满,生成新的音符!"); + GameObject obj = Instantiate(prefab); + obj.SetActive(true); + return obj; + } + } + } + + private void ReturnObjectToPool(Stack pool, GameObject obj) + { + if (obj == null || pool.Contains(obj)) return; // 防止重复回收 + + if (obj != null) + { + obj.SetActive(false); // 设置为非活动状态 + if (pool.Count < maxPoolSize) + { + pool.Push(obj); + //Debug.Log($"已归还对象:{obj.name},当前池容量:{pool.Count}"); + } + else + { + //Debug.LogWarning("对象池已满,无法继续归还对象!"); + } + } + } + + private void AddToPool(Stack pool, GameObject prefab) + { + if (pool.Count < maxPoolSize) + { + GameObject obj = Instantiate(prefab); + obj.SetActive(false); + pool.Push(obj); + } + } + + public GameObject GetNote(string color) + { + + int colorIndex = GetColorIndexFromName(color); + return GetObjectFromPool(notePools[colorIndex], notePrefabs[colorIndex]); + } + + public GameObject GetStartNote(string color) + { + + int colorIndex = GetColorIndexFromName(color); + return GetObjectFromPool(startNotePools[colorIndex], startNotePrefabs[colorIndex]); + } + + public GameObject GetHoldNoteSegment(string color) + { + + int colorIndex = GetColorIndexFromName(color); + return GetObjectFromPool(holdNotePools[colorIndex], holdNotePrefabs[colorIndex]); + } + + public void ReturnNote(GameObject note, string color) + { + Note noteComponent = note.GetComponent(); + if (noteComponent != null) + { + JudgeManager.Instance?.UnregisterNote(noteComponent.GetKey(), noteComponent); + } + + NoteController noteController = note.GetComponent(); + if (noteController != null) + { + noteController.ResetState(); + } + + ReturnObjectToPool(notePools[GetColorIndexFromName(color)], note); + } + + public void ReturnStartNote(GameObject startNote, string color) + { + if (string.IsNullOrEmpty(color)) + { + //Debug.LogWarning($" 归还 StartNote 时 color 为空!音符名: {startNote.name}"); + return; + } + + HoldNote holdNote = startNote.GetComponent(); + if (holdNote != null) + { + holdNote.ResetState(); // 确保重置音符状态 + } + + ReturnObjectToPool(startNotePools[GetColorIndexFromName(color)], startNote); + } + + public void ReturnHoldNoteSegment(GameObject holdNote, string color) + { + if (string.IsNullOrEmpty(color)) + { + //Debug.LogWarning($" 归还 HoldNoteSegment 时 color 为空!音符名: {holdNote.name}"); + return; + } + + HoldNote holdNoteScript = holdNote.GetComponent(); + if (holdNoteScript != null) + { + holdNoteScript.ResetState(); // 确保重置音符状态 + } + + ReturnObjectToPool(holdNotePools[GetColorIndexFromName(color)], holdNote); + } + + + private int GetColorIndexFromName(string colorName) + { + + switch (colorName) + { + case "red": return 0; + case "green": return 1; + case "yellow": return 2; + case "purple": return 3; + case "blue": return 4; + default: + //Debug.LogError($"未识别的颜色: {colorName},默认使用红色"); + return 0; + } + } +} diff --git a/Assets/scripts/gamePlay_gameplay/NotePool.cs.meta b/Assets/scripts/gamePlay_gameplay/NotePool.cs.meta new file mode 100644 index 00000000..1ba825c7 --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/NotePool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f4db5764af5850449f3b6314af7fb46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/scripts/gamePlay_gameplay/NoteSpawner.cs b/Assets/scripts/gamePlay_gameplay/NoteSpawner.cs new file mode 100644 index 00000000..1de96132 --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/NoteSpawner.cs @@ -0,0 +1,247 @@ +锘縰sing UnityEngine; +using System.Collections; +using TMPro; + +public class NoteSpawner : MonoBehaviour +{ + public NotePool notePool; // 寮曠敤 NotePool + public Transform[] spawnPoints; // 瀵瑰簲杞ㄩ亾鐨勭敓鎴愮偣 + public GameObject[] notePrefabs; // 鐭煶绗﹂鍒朵綋 + + public TextMeshProUGUI globalGameTime; + + // 闀块煶绗﹂鍒朵綋鏁扮粍锛堝垎鍒负 start, middle, end锛 + public GameObject[] holdNoteStartPrefabs; + public GameObject[] holdNoteMiddlePrefabs; + public GameObject[] holdNoteEndPrefabs; + + public float spawnOffset = 0f; // 鐢熸垚闊崇鏃剁殑鏃堕棿鍋忕Щ閲 + public float bpm; + + private HoldNote _holdNoteComponentCache; // 缂撳瓨HoldNote缁勪欢 + private Beatmap beatmap; + private float startTime; // 瀛樺偍姝屾洸寮濮嬫椂闂 + private bool isSpawning = false; + + private void Awake() + { + // 鍒濆鍖朒oldNote缁勪欢缂撳瓨 + if (holdNoteStartPrefabs != null && holdNoteStartPrefabs.Length > 0) + { + _holdNoteComponentCache = holdNoteStartPrefabs[0].GetComponent(); + } + } + + public void LoadBeatmap(Beatmap loadedBeatmap) + { + if (isSpawning) return; + isSpawning = true; + + beatmap = loadedBeatmap; + if (beatmap == null) + { + //Debug.LogError("鍔犺浇鐨勮氨闈负绌猴紒"); + return; + } + + bpm = beatmap.bpm; + startTime = Time.time; + //Debug.Log($"姝屾洸寮濮嬫椂闂: {startTime}"); + StartCoroutine(SpawnNotes()); + } + + private IEnumerator SpawnNotes() + { + if (beatmap == null || beatmap.notes == null) + { + //Debug.LogError("璋遍潰鏁版嵁涓虹┖锛"); + isSpawning = false; + yield break; + } + + foreach (NoteData note in beatmap.notes) + { + float travelTime = (60f / bpm) * 4; + float spawnTime = note.time - travelTime; + float delay = spawnTime - (Time.time - startTime) + spawnOffset; + Debug.Log($"delay: {delay}"); + + if (delay > 0) + yield return new WaitForSeconds(delay); + + if (note.type == "hold") + { + SpawnHoldNote(note); + } + else + { + SpawnNote(note); + } + } + + isSpawning = false; + } + + // 鐢熸垚鐭煶绗 + public void SpawnNote(NoteData noteData) + { + if (noteData.trackIndex < 0 || noteData.trackIndex >= spawnPoints.Length) + { + //Debug.LogError("杞ㄩ亾绱㈠紩瓒呭嚭鑼冨洿锛"); + return; + } + + KeyCode key = KeyBindingManager.GetKeyForColor(noteData.color); + if (key == KeyCode.None) + { + //Debug.LogError($"鏈壘鍒伴鑹 {noteData.color} 瀵瑰簲鐨勬寜閿紒璇锋鏌 KeyBindingManager 鏄惁姝g‘鍒濆鍖栥"); + return; + } + + //Debug.Log($"鐢熸垚鐭煶绗︼細棰滆壊={noteData.color}, 鎸夐敭={key}, 杞ㄩ亾={noteData.trackIndex}"); + + GameObject note = notePool.GetNote(noteData.color); + if (note == null) + { + //Debug.LogError("瀵硅薄姹犺繑鍥炰簡涓涓┖闊崇锛"); + return; + } + + note.transform.position = spawnPoints[noteData.trackIndex].position; + note.transform.rotation = Quaternion.identity; + + Note noteScript = note.GetComponent(); + if (noteScript != null) + { + float noteSpawnTime = Time.time; + noteScript.Setup(key, noteData.trackIndex, CalculateSpeed(), noteData.time, noteData.color); + //Debug.Log($"鍒拌揪鏃堕棿锛歿noteSpawnTime + (60f / bpm) * 4}"); + } + else + { + //Debug.LogError("闊崇棰勫埗浣撶己灏 Note 缁勪欢锛"); + } + } + + public void SpawnHoldNote(NoteData noteData) + { + if (noteData == null) + { + //Debug.LogError("NoteData涓虹┖锛"); + return; + } + + if (noteData.trackIndex < 0 || noteData.trackIndex >= spawnPoints.Length) + { + //Debug.LogError("杞ㄩ亾绱㈠紩瓒呭嚭鑼冨洿锛"); + return; + } + + KeyCode key = KeyBindingManager.GetKeyForColor(noteData.color); + if (key == KeyCode.None) + { + //Debug.LogError($"鏈壘鍒伴鑹 {noteData.color} 瀵瑰簲鐨勬寜閿紒"); + return; + } + + if (string.IsNullOrEmpty(noteData.color)) + { + //Debug.LogError("[NoteSpawner] noteData.color 涓虹┖锛屾棤娉曠敓鎴愰煶绗︼紒"); + return; + } + + //Debug.Log($"鐢熸垚鏃堕棿锛歿Time.time}"); + //Debug.Log($"鐢熸垚闀块煶绗︼細棰滆壊={noteData.color}, 鎸夐敭={key}, 杞ㄩ亾={noteData.trackIndex}"); + + float segmentInterval = (60f / bpm) / 4f; + int segmentCount = Mathf.CeilToInt(noteData.length / segmentInterval); + Transform spawnPoint = spawnPoints[noteData.trackIndex]; + float scheduledEndTime = noteData.time + noteData.length; + + // 鐢熸垚鍞竴ID + int holdNoteId = GenerateHoldNoteId(); + + // 鐢熸垚 Start 娈 + GameObject startObj = notePool.GetStartNote(noteData.color); + if (startObj == null) + { + //Debug.LogError("瀵硅薄姹犺繑鍥炵┖ hold note start锛"); + return; + } + + SetupHoldNoteSegment(startObj, spawnPoint, holdNoteId, noteData, key, 0f, false, "start", scheduledEndTime); + + // 鐢熸垚 Middle 鍜 End 娈 + for (int i = 1; i < segmentCount; i++) + { + float segmentDelay = i * segmentInterval; + bool isEndSegment = (i == segmentCount - 1); + string partType = isEndSegment ? "end" : "middle"; + + // 搴旂敤gracePeriod + segmentDelay = CalculateSegmentDelay(segmentDelay, isEndSegment); + + GameObject segObj = notePool.GetHoldNoteSegment(noteData.color); + if (segObj == null) + { + Debug.LogError("瀵硅薄姹犺繑鍥炵┖ hold note 鐗囨锛"); + continue; + } + + SetupHoldNoteSegment(segObj, spawnPoint, holdNoteId, noteData, key, segmentDelay, isEndSegment, partType, scheduledEndTime); + } + } + + private int GenerateHoldNoteId() + { + return System.Guid.NewGuid().GetHashCode() & 0x7FFFFFFF; // 纭繚鐢熸垚姝f暟ID + } + + private float CalculateSegmentDelay(float delay, bool isEndSegment) + { + if (!isEndSegment && _holdNoteComponentCache != null) + { + delay -= _holdNoteComponentCache.segmentGracePeriod; + return Mathf.Max(delay, 0); + } + return delay; + } + + private void SetupHoldNoteSegment(GameObject noteObj, Transform spawnPoint, int holdNoteId, + NoteData noteData, KeyCode key, float delay, + bool isEnd, string type, float scheduledEndTime) + { + noteObj.transform.position = spawnPoint.position; + noteObj.transform.rotation = Quaternion.identity; + + HoldNote holdNote = noteObj.GetComponent(); + if (holdNote != null) + { + holdNote.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), + noteData.time, delay, isEnd, scheduledEndTime, + noteData.color, key, type); + + // 娉ㄥ唽缁撴潫鏃堕棿 + if (isEnd) + { + JudgeManager.Instance.RegisterScheduledEndTime(holdNoteId.ToString(), scheduledEndTime); + } + } + else + { + //Debug.LogError("闀块煶绗︾墖娈电己灏 HoldNote 缁勪欢锛"); + } + } + + private float CalculateSpeed() + { + float noteTravelTime = (60f / bpm) * 4; + return 10.75f / noteTravelTime; + } + + public void ResetAllHoldNotes() + { + JudgeManager.Instance.ResetAllHoldNotes(); + //Debug.Log("閲嶇疆鎵鏈夐暱闊崇鐘舵"); + } +} diff --git a/Assets/scripts/gamePlay_gameplay/NoteSpawner.cs.meta b/Assets/scripts/gamePlay_gameplay/NoteSpawner.cs.meta new file mode 100644 index 00000000..933d22b7 --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/NoteSpawner.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a8261645f54b8647ba4c89adc503be9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: