修改部分的长音符判定bug,优化多键位同时触发的判定逻辑
Signed-off-by: Jiangqvweihuan <>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
public class InputManager : MonoBehaviour
|
||||
{
|
||||
public static event Action<KeyCode> OnKeyPressed;
|
||||
public static event Action<KeyCode> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2c4f957042317641bf529c141c39b91
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,196 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class JudgeManager : MonoBehaviour
|
||||
{
|
||||
public static JudgeManager Instance { get; private set; }
|
||||
|
||||
private Dictionary<KeyCode, Queue<Note>> judgeQueues = new Dictionary<KeyCode, Queue<Note>>();
|
||||
private Dictionary<string, bool> startJudgedNotes = new Dictionary<string, bool>();
|
||||
private Dictionary<string, bool> releasedNotes = new Dictionary<string, bool>();
|
||||
private Dictionary<string, float> noteEndTimes = new Dictionary<string, float>();
|
||||
private Dictionary<string, float> _holdNoteStartTimes = new Dictionary<string, float>();
|
||||
|
||||
// 长音符状态管理
|
||||
private Dictionary<int, bool> _holdNoteValidity = new Dictionary<int, bool>();
|
||||
private Dictionary<int, bool> _holdNoteKeyHeld = new Dictionary<int, bool>(); // 新增:记录按键按住状态
|
||||
private Dictionary<KeyCode, List<Note>> multiKeyJudgeQueues = new Dictionary<KeyCode, List<Note>>();
|
||||
|
||||
|
||||
|
||||
public void RegisterNoteForMultiKey(KeyCode key, Note note)
|
||||
{
|
||||
if (!multiKeyJudgeQueues.ContainsKey(key))
|
||||
multiKeyJudgeQueues[key] = new List<Note>();
|
||||
|
||||
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<Note>();
|
||||
|
||||
if (!judgeQueues[key].Contains(note))
|
||||
{
|
||||
judgeQueues[key].Enqueue(note);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterNote(KeyCode key, Note note)
|
||||
{
|
||||
if (!judgeQueues.ContainsKey(key)) return;
|
||||
|
||||
Queue<Note> newQueue = new Queue<Note>();
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e726455158177b3499eb8f0910c54e30
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class KeyBindingManager : MonoBehaviour
|
||||
{
|
||||
private static Dictionary<string, KeyCode> keyBindings = new Dictionary<string, KeyCode>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
//Debug.Log("KeyBindingManager Awake() 被调用,开始加载按键映射...");
|
||||
|
||||
if (keyBindings.Count == 0)
|
||||
{
|
||||
LoadKeyBindings();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> 获取颜色对应的按键 </summary>
|
||||
public static KeyCode GetKeyForColor(string color)
|
||||
{
|
||||
if (keyBindings.TryGetValue(color.ToLower(), out KeyCode key))
|
||||
{
|
||||
return key;
|
||||
}
|
||||
//Debug.LogError($"未找到颜色 {color} 对应的按键!请检查 KeyBindingManager 是否正确初始化。");
|
||||
return KeyCode.None;
|
||||
}
|
||||
|
||||
/// <summary> 修改按键绑定 </summary>
|
||||
public static void ChangeKeyBinding(string color, KeyCode newKey)
|
||||
{
|
||||
if (keyBindings.ContainsKey(color.ToLower()))
|
||||
{
|
||||
keyBindings[color.ToLower()] = newKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
keyBindings.Add(color.ToLower(), newKey);
|
||||
}
|
||||
|
||||
SaveKeyBindings();
|
||||
}
|
||||
|
||||
/// <summary> 存储按键绑定到 `PlayerPrefs` </summary>
|
||||
private static void SaveKeyBindings()
|
||||
{
|
||||
foreach (var kvp in keyBindings)
|
||||
{
|
||||
PlayerPrefs.SetInt($"KeyBinding_{kvp.Key}", (int)kvp.Value);
|
||||
}
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
/// <summary> 从 `PlayerPrefs` 加载按键绑定 </summary>
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2639b2e07c4995842bdbe955d9fb6bfb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d8062cc45ed8f84da8460fcd2964b77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<AnimationController>();
|
||||
controller = GetComponent<NoteController>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 由 Controller 调用,通知 Note 是否在判定区域内
|
||||
/// </summary>
|
||||
public void SetJudgeZone(bool inZone)
|
||||
{
|
||||
// 如果音符离开判定区域且还未判定,则判为 Miss
|
||||
if (!inZone && !isJudged)
|
||||
{
|
||||
JudgeMiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7d5a4ca6fc6b784d89e910ff90c2c7a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<int, Stack<GameObject>> notePools; // 短音符对象池
|
||||
private Dictionary<int, Stack<GameObject>> holdNotePools; // 长音符片段对象池
|
||||
private Dictionary<int, Stack<GameObject>> startNotePools; // 长音符start片段对象池
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
notePools = new Dictionary<int, Stack<GameObject>>();
|
||||
holdNotePools = new Dictionary<int, Stack<GameObject>>();
|
||||
startNotePools = new Dictionary<int, Stack<GameObject>>();
|
||||
|
||||
for (int i = 0; i < notePrefabs.Length; i++)
|
||||
{
|
||||
notePools[i] = new Stack<GameObject>();
|
||||
holdNotePools[i] = new Stack<GameObject>();
|
||||
startNotePools[i] = new Stack<GameObject>();
|
||||
|
||||
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<GameObject> 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<GameObject> 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<GameObject> 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<Note>();
|
||||
if (noteComponent != null)
|
||||
{
|
||||
JudgeManager.Instance?.UnregisterNote(noteComponent.GetKey(), noteComponent);
|
||||
}
|
||||
|
||||
NoteController noteController = note.GetComponent<NoteController>();
|
||||
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<HoldNote>();
|
||||
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<HoldNote>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f4db5764af5850449f3b6314af7fb46
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,247 @@
|
||||
using 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()
|
||||
{
|
||||
// 初始化HoldNote组件缓存
|
||||
if (holdNoteStartPrefabs != null && holdNoteStartPrefabs.Length > 0)
|
||||
{
|
||||
_holdNoteComponentCache = holdNoteStartPrefabs[0].GetComponent<HoldNote>();
|
||||
}
|
||||
}
|
||||
|
||||
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 是否正确初始化。");
|
||||
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<Note>();
|
||||
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; // 确保生成正数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<HoldNote>();
|
||||
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("重置所有长音符状态");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a8261645f54b8647ba4c89adc503be9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user