repaired the motherfucking hold note problem which troubled us longer than 2 months
added some UI
This commit is contained in:
@@ -2,9 +2,6 @@ using UnityEngine;
|
||||
|
||||
public abstract class BaseNote : MonoBehaviour
|
||||
{
|
||||
// 新增:控制音符显示状态
|
||||
[System.NonSerialized] // 避免暴露在Inspector
|
||||
public bool shouldBeVisible = true;
|
||||
public int TrackIndex { get; protected set; }
|
||||
public string NoteColor { get; protected set; }
|
||||
public float Speed { get; protected set; }
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1685ef287fcd6ee449c4bc269594b792
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 1685ef287fcd6ee449c4bc269594b792
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -6,22 +6,22 @@ using UnityEngine.UI;
|
||||
public class BeatmapManager : MonoBehaviour
|
||||
|
||||
{
|
||||
public Beatmap beatmap; // 当前谱面数据
|
||||
public Beatmap beatmap; // 当前谱面数据
|
||||
|
||||
// 音符生成器引用
|
||||
// 音符生成器引用
|
||||
public NoteSpawner noteSpawner;
|
||||
|
||||
|
||||
|
||||
// 保存谱面数据到 JSON 文件
|
||||
// 保存谱面数据到 JSON 文件
|
||||
//public void SaveBeatmap(string fileName)
|
||||
//{
|
||||
// string json = JsonUtility.ToJson(beatmap, true); // 格式化 JSON 输出
|
||||
// string json = JsonUtility.ToJson(beatmap, true); // 格式化 JSON 输出
|
||||
// File.WriteAllText(Application.dataPath + "/" + fileName, json);
|
||||
// Debug.Log("谱面已保存:" + Application.dataPath + "/" + fileName);
|
||||
// Debug.Log("谱面已保存:" + Application.dataPath + "/" + fileName);
|
||||
//}
|
||||
|
||||
// 从 JSON 文件加载谱面数据
|
||||
// 从 JSON 文件加载谱面数据
|
||||
public void LoadBeatmap(string fileName)
|
||||
{
|
||||
string path = Application.streamingAssetsPath + "/" + fileName;
|
||||
@@ -29,23 +29,23 @@ public class BeatmapManager : MonoBehaviour
|
||||
{
|
||||
string json = File.ReadAllText(path);
|
||||
beatmap = JsonUtility.FromJson<Beatmap>(json);
|
||||
//Debug.Log("谱面已加载:" + json);
|
||||
Debug.Log("谱面已加载:" + json);
|
||||
|
||||
// 在加载后立即调用音符生成
|
||||
// 在加载后立即调用音符生成
|
||||
noteSpawner.LoadBeatmap(beatmap);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.LogError("文件不存在:" + path);
|
||||
Debug.LogError("文件不存在:" + path);
|
||||
}
|
||||
}
|
||||
public void LoadBeatmap(Beatmap loadedBeatmap)
|
||||
{
|
||||
beatmap = loadedBeatmap;
|
||||
//Debug.Log("谱面已加载:" + beatmap.title);
|
||||
Debug.Log("谱面已加载:" + beatmap.title);
|
||||
|
||||
// 在加载后立即调用音符生成
|
||||
// 在加载后立即调用音符生成
|
||||
noteSpawner.LoadBeatmap(beatmap);
|
||||
}
|
||||
|
||||
@@ -56,15 +56,15 @@ public class BeatmapManager : MonoBehaviour
|
||||
// {
|
||||
// string json = File.ReadAllText(path);
|
||||
// Beatmap beatmap = JsonUtility.FromJson<Beatmap>(json);
|
||||
// beatmapManager.LoadBeatmap(beatmap); // 使用 LoadBeatmap 方法传递 Beatmap 对象
|
||||
// beatmapManager.LoadBeatmap(beatmap); // 使用 LoadBeatmap 方法传递 Beatmap 对象
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Debug.LogError("文件不存在:" + path);
|
||||
// Debug.LogError("文件不存在:" + path);
|
||||
// }
|
||||
//}
|
||||
|
||||
// 创建一个示例谱面并保存
|
||||
// 创建一个示例谱面并保存
|
||||
//public void CreateSampleBeatmap()
|
||||
//{
|
||||
// beatmap = new Beatmap
|
||||
@@ -105,7 +105,7 @@ public class BeatmapManager : MonoBehaviour
|
||||
// SaveBeatmap("test_beatmap.json");
|
||||
//}
|
||||
|
||||
// 读取并加载当前谱面到音符生成器
|
||||
// 读取并加载当前谱面到音符生成器
|
||||
public void LoadBeatmapFromFile()
|
||||
{
|
||||
string path = Application.streamingAssetsPath + "/Emilia_demo.json";
|
||||
@@ -117,15 +117,15 @@ public class BeatmapManager : MonoBehaviour
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.LogError("谱面文件未找到!");
|
||||
Debug.LogError("谱面文件未找到!");
|
||||
}
|
||||
}
|
||||
|
||||
// 在 Start() 方法中初始化
|
||||
// 在 Start() 方法中初始化
|
||||
void Start()
|
||||
{
|
||||
// 这里可以选择在启动时创建一个示例谱面并加载
|
||||
//CreateSampleBeatmap(); // 创建并保存一个示例谱面
|
||||
LoadBeatmap("Emilia_demo.json"); // 加载并实例化音符
|
||||
// 这里可以选择在启动时创建一个示例谱面并加载
|
||||
//CreateSampleBeatmap(); // 创建并保存一个示例谱面
|
||||
LoadBeatmap("Emilia_demo.json"); // 加载并实例化音符
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c80308880c1feca40bcf476848142e2b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: c80308880c1feca40bcf476848142e2b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -1,69 +1,15 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using static HoldNote;
|
||||
public enum NoteScore
|
||||
{
|
||||
Perfect,
|
||||
Good,
|
||||
Okay,
|
||||
Miss
|
||||
}
|
||||
|
||||
public class GameManager : MonoBehaviour
|
||||
{
|
||||
public static GameManager Instance { get; private set; }
|
||||
public BeatmapManager beatmapManager; // 负责加载和管理谱面
|
||||
public NoteSpawner noteSpawner; // 音符生成器
|
||||
public AudioSource musicSource; // 音乐播放器
|
||||
public int CurrentScore { get; private set; }
|
||||
public int MaxCombo { get; private set; }
|
||||
public int CurrentCombo { get; private set; }
|
||||
|
||||
// 添加计分方法
|
||||
public void AddScore(NoteScore scoreType)
|
||||
{
|
||||
// 计分逻辑
|
||||
switch (scoreType)
|
||||
{
|
||||
case NoteScore.Perfect:
|
||||
CurrentScore += 100;
|
||||
Debug.Log("Perfect! +100");
|
||||
break;
|
||||
case NoteScore.Good:
|
||||
CurrentScore += 80;
|
||||
Debug.Log("Good! +80");
|
||||
break;
|
||||
case NoteScore.Okay:
|
||||
CurrentScore += 50;
|
||||
Debug.Log("Okay +50");
|
||||
break;
|
||||
case NoteScore.Miss:
|
||||
CurrentCombo = 0; // 连击中断
|
||||
Debug.Log("Miss! 连击中断");
|
||||
return; // 不增加分数
|
||||
}
|
||||
|
||||
// 连击处理
|
||||
CurrentCombo++;
|
||||
if (CurrentCombo > MaxCombo)
|
||||
MaxCombo = CurrentCombo;
|
||||
}
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
}
|
||||
void Start()
|
||||
{
|
||||
//Debug.Log("GameManager Start() 被调用");
|
||||
Debug.Log("GameManager Start() 被调用");
|
||||
|
||||
// 检查关键组件是否为空
|
||||
if (beatmapManager == null) Debug.LogError("beatmapManager 未赋值!");
|
||||
@@ -75,7 +21,7 @@ public class GameManager : MonoBehaviour
|
||||
|
||||
if (!File.Exists(beatmapFilePath))
|
||||
{
|
||||
//Debug.LogError($"谱面文件不存在: {beatmapFilePath}");
|
||||
Debug.LogError($"谱面文件不存在: {beatmapFilePath}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,26 +30,20 @@ public class GameManager : MonoBehaviour
|
||||
|
||||
if (beatmap == null)
|
||||
{
|
||||
//Debug.LogError("谱面解析失败,JSON 格式可能错误!");
|
||||
Debug.LogError("谱面解析失败,JSON 格式可能错误!");
|
||||
return;
|
||||
}
|
||||
|
||||
beatmapManager.LoadBeatmap(beatmap); // 传递给 BeatmapManager
|
||||
noteSpawner.LoadBeatmap(beatmap); // 传递给 NoteSpawner
|
||||
|
||||
|
||||
// 播放背景音乐
|
||||
if (!string.IsNullOrEmpty(beatmap.musicFile))
|
||||
{
|
||||
string musicPath = Path.Combine("song_songIndex/1002001_emilia", Path.GetFileNameWithoutExtension(beatmap.musicFile));
|
||||
AudioClip musicClip = Resources.Load<AudioClip>(musicPath);
|
||||
|
||||
AudioClip musicClip = Resources.Load<AudioClip>(beatmap.musicFile);
|
||||
if (musicClip == null)
|
||||
{
|
||||
//Debug.LogError($"音乐文件加载失败,尝试路径: {musicPath}");
|
||||
// 列出Resources文件夹下所有音频文件用于调试
|
||||
var allAudio = Resources.LoadAll<AudioClip>("");
|
||||
//Debug.Log("可用音频文件:" + string.Join(", ", allAudio.Select(a => a.name)));
|
||||
Debug.LogError($"音乐文件加载失败: {beatmap.musicFile}");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -113,7 +53,7 @@ public class GameManager : MonoBehaviour
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.LogError("谱面中 musicFile 为空!");
|
||||
Debug.LogError("谱面中 musicFile 为空!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a3cb3ef9478edd4a8b6718a11a0fc3c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 0a3cb3ef9478edd4a8b6718a11a0fc3c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public enum NoteSegment { None, Start, Middle, End }
|
||||
@@ -7,30 +6,19 @@ public enum NoteSegment { None, Start, Middle, End }
|
||||
[RequireComponent(typeof(HoldNoteController))]
|
||||
public class HoldNote : BaseNote
|
||||
{
|
||||
[Header("判定容差")]
|
||||
public float startHitWindow = 0.1f; // 起始段判定窗口
|
||||
public float endHitWindow = 0.15f; // 结束段判定窗口
|
||||
public float segmentGracePeriod = 0.05f; // 分段间宽限时间
|
||||
|
||||
private KeyCode keyToPress = KeyCode.None;
|
||||
private string noteColor = string.Empty;
|
||||
private string noteID = string.Empty;
|
||||
private NoteSegment segment = NoteSegment.None;
|
||||
|
||||
private new float hitTime = 0f;
|
||||
private float hitTime = 0f;
|
||||
private float releaseTime = 0f;
|
||||
private bool hasReleased = true;
|
||||
private bool hasEnteredLine = false;
|
||||
|
||||
private Coroutine autoReturnCoroutine;
|
||||
private HoldNoteController controller;
|
||||
private static Dictionary<int, bool> _holdNoteValidity = new Dictionary<int, bool>();
|
||||
|
||||
// 新增变量:按键按住检测
|
||||
private bool _isKeyHeld = false;
|
||||
private float _lastKeyPressTime = 0f;
|
||||
private const float KEY_CHECK_INTERVAL = 0.05f;
|
||||
private Coroutine _keyCheckCoroutine;
|
||||
private AnimationController anim;
|
||||
|
||||
public int id;
|
||||
public int trackIndex;
|
||||
@@ -43,9 +31,16 @@ public class HoldNote : BaseNote
|
||||
public KeyCode key;
|
||||
public string type; // "start", "middle", or "end"
|
||||
|
||||
private bool isJudged = false; // 分段判定标记,一旦判定(无论成功失败)就设为true,避免重复判定
|
||||
private bool isHoldActive = false; // 长按有效标记:Start段成功判定且按键未松开
|
||||
|
||||
// 新增变量
|
||||
private bool hasBeenHeldFromStart = false; // 进入判定区时是否已按住 (用于辅助判断,但不再直接阻断判定)
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
controller = GetComponent<HoldNoteController>();
|
||||
anim = GetComponent<AnimationController>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
@@ -53,6 +48,25 @@ public class HoldNote : BaseNote
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private void PlayHitAnimation()
|
||||
{
|
||||
if (anim != null)
|
||||
{
|
||||
// 激活动画效果 GameObject
|
||||
anim.gameObject.SetActive(true);
|
||||
anim.PlayDestroyAnimation(noteColor);
|
||||
|
||||
// 延迟禁用动画效果 GameObject,确保动画播放完毕
|
||||
StartCoroutine(DelayedDisableAnimation(anim.gameObject, 0.5f)); // 0.5秒后禁用
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator DelayedDisableAnimation(GameObject animationObject, float delay)
|
||||
{
|
||||
yield return new WaitForSeconds(delay);
|
||||
animationObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void Setup(int id, int trackIndex, float speed, float time, float delay,
|
||||
bool isEnd, float scheduledEnd, string color, KeyCode key, string type)
|
||||
{
|
||||
@@ -72,123 +86,134 @@ public class HoldNote : BaseNote
|
||||
this.noteColor = color;
|
||||
this.hitTime = time + delay;
|
||||
this.segment = (delay == 0f) ? NoteSegment.Start :
|
||||
(isEnd ? NoteSegment.End : NoteSegment.Middle);
|
||||
(isEnd ? NoteSegment.End : NoteSegment.Middle);
|
||||
this.hasEnteredLine = false;
|
||||
this.hasReleased = false;
|
||||
|
||||
// 在 Setup 时注册初始状态
|
||||
JudgeManager.Instance?.RegisterNoteReleased(noteID, false);
|
||||
if (segment == NoteSegment.Start)
|
||||
JudgeManager.Instance?.RegisterStartJudged(noteID, false);
|
||||
JudgeManager.Instance?.RegisterStartJudged(noteID, false); // 初始为未判定
|
||||
|
||||
if (segment == NoteSegment.End)
|
||||
JudgeManager.Instance?.RegisterScheduledEndTime(noteID, scheduledEndTime);
|
||||
|
||||
// 如果是起始段,初始化状态为true
|
||||
if (type == "start")
|
||||
{
|
||||
_holdNoteValidity[id] = true;
|
||||
}
|
||||
|
||||
// 立即检查有效性
|
||||
if (type != "start" && (!_holdNoteValidity.ContainsKey(id) || !_holdNoteValidity[id]))
|
||||
{
|
||||
JudgeMiss();
|
||||
}
|
||||
|
||||
controller?.SetSpeed(speed);
|
||||
controller?.SetSegmentDelay(delay);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// 检查长音符是否有效
|
||||
if (!IsHoldNoteValid()) return;
|
||||
// 如果当前片段已经判定过,直接返回
|
||||
if (isJudged) return;
|
||||
|
||||
if (segment == NoteSegment.Start && Input.GetKeyDown(keyToPress))
|
||||
// **通用长按状态更新:**
|
||||
// 只有当isHoldActive为true时才检查松开操作,避免误判
|
||||
// 且仅在GetKeyUp时更新状态,避免重复操作
|
||||
if (isHoldActive && Input.GetKeyUp(keyToPress))
|
||||
{
|
||||
if (hasEnteredLine)
|
||||
isHoldActive = false;
|
||||
Debug.Log($"[HoldNote] 按键 {keyToPress} 松开,长按状态失效。NoteID: {noteID}, Segment: {segment}, Type: {type}");
|
||||
// 如果是中间段或结束段,且因为松手导致长按失效,可以立即回收
|
||||
if (segment == NoteSegment.Middle || segment == NoteSegment.End)
|
||||
{
|
||||
// 注意:End段的判定在HandleEnd里,松手只是让isHoldActive为false,不代表End段判定已完成
|
||||
// 如果 End 段的判定窗口还没过,松手后 isHoldActive = false,End 段在 OnTriggerExit2D 才会回收
|
||||
// 或者在 HandleEnd 之前松手导致长按失效,HandleEnd 内部会判定 Miss
|
||||
}
|
||||
}
|
||||
// 如果按键被按下,且Start段已经判定成功,则激活长按状态
|
||||
// 考虑到玩家可能在Start段通过后瞬间松手再按下,这里需要确保只要Start段判定成功且按键按住,isHoldActive就为true
|
||||
if (JudgeManager.Instance.IsStartJudged(noteID) && Input.GetKey(keyToPress))
|
||||
{
|
||||
isHoldActive = true;
|
||||
}
|
||||
|
||||
|
||||
// Start段判定
|
||||
if (segment == NoteSegment.Start && hasEnteredLine)
|
||||
{
|
||||
// 在判定窗口内按下按键才触发判定
|
||||
if (Input.GetKeyDown(keyToPress))
|
||||
{
|
||||
HandleStart();
|
||||
isJudged = true; // 无论成功失败都标记为已判定,避免重复尝试
|
||||
}
|
||||
// 如果Start段进入判定区但玩家未按键,且已经错过最佳判定时机,可以考虑Miss并回收
|
||||
// 这部分逻辑现在放在 OnTriggerExit2D 里处理更合适
|
||||
}
|
||||
else if (segment == NoteSegment.End && Input.GetKeyUp(keyToPress))
|
||||
// Middle段判定:
|
||||
// 只有当头部已判定、且长按有效(按键持续按住),并且已经到达该Middle段的命中时间点时,才认为该Middle段通过。
|
||||
// Middle段本身没有独立的按键输入判定,它只是长按的延续。
|
||||
else if (segment == NoteSegment.Middle
|
||||
&& JudgeManager.Instance.IsStartJudged(noteID) // 头部必须已判定
|
||||
&& isHoldActive // 必须处于长按有效状态(按键持续按住)
|
||||
&& !JudgeManager.Instance.HasNoteReleased(noteID)) // 确保End段还未被判定释放
|
||||
{
|
||||
HandleEnd();
|
||||
}
|
||||
|
||||
// 更新按键状态
|
||||
if (segment == NoteSegment.Middle && hasEnteredLine)
|
||||
{
|
||||
if (JudgeManager.Instance.IsStartJudged(noteID) &&
|
||||
!JudgeManager.Instance.HasNoteReleased(noteID))
|
||||
// Debug.Log($"[HoldNote] Middle段状态检查: Time.time={Time.time:F2}, hitTime={hitTime:F2}, hasEnteredLine={hasEnteredLine}");
|
||||
if (Time.time >= hitTime)
|
||||
{
|
||||
if (!Input.GetKey(keyToPress))
|
||||
// 确保它进入了判定线,但在Update中,即使没完全进入,只要时间到了且条件满足,也算通过
|
||||
// 但是,我们主要希望它在通过判定线后被回收
|
||||
// 因此,Middle段的回收逻辑更多依赖于DelayedAutoReturnCheck或OnTriggerExit2D
|
||||
if (hasEnteredLine) // 确保已经进入过判定线,防止音符提前消失
|
||||
{
|
||||
// 中途松开按键,判定失败
|
||||
_holdNoteValidity[id] = false;
|
||||
JudgeMiss();
|
||||
Debug.Log($"[HoldNote] Middle段通过 (持续长按): {noteColor}");
|
||||
PlayHitAnimation();
|
||||
ReturnToPool();
|
||||
isJudged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsHoldNoteValid()
|
||||
{
|
||||
// 起始段总是有效(因为它决定有效性)
|
||||
if (segment == NoteSegment.Start) return true;
|
||||
|
||||
// 其他段检查有效性
|
||||
return _holdNoteValidity.ContainsKey(id) && _holdNoteValidity[id];
|
||||
}
|
||||
|
||||
private IEnumerator CheckKeyHold()
|
||||
{
|
||||
while (true)
|
||||
// End段判定
|
||||
else if (segment == NoteSegment.End
|
||||
&& JudgeManager.Instance.IsStartJudged(noteID) // 头部必须已判定
|
||||
&& !JudgeManager.Instance.HasNoteReleased(noteID)) // 确保还未被判定释放
|
||||
{
|
||||
if (Input.GetKey(keyToPress))
|
||||
// 只有当玩家松开按键时才触发End段判定
|
||||
if (Input.GetKeyUp(keyToPress))
|
||||
{
|
||||
_isKeyHeld = true;
|
||||
_lastKeyPressTime = Time.time;
|
||||
HandleEnd();
|
||||
isJudged = true; // 标记为已判定
|
||||
}
|
||||
else if (_isKeyHeld && Time.time - _lastKeyPressTime > KEY_CHECK_INTERVAL)
|
||||
// 如果 End 段已经达到或超过了预定的释放时间 (scheduledEndTime),
|
||||
// 但玩家仍未松手,且长按状态仍然有效,这可能是玩家按得过长
|
||||
// 这种情况应该给予一个差的判定或者Miss
|
||||
else if (Time.time >= scheduledEndTime + 0.1f && isHoldActive) // 给予一点容错时间
|
||||
{
|
||||
// 按键松开超过检查间隔,判定为松开
|
||||
_isKeyHeld = false;
|
||||
if (segment == NoteSegment.Middle || segment == NoteSegment.End)
|
||||
{
|
||||
// 中间段或结束段松开按键,标记长音符无效
|
||||
_holdNoteValidity[id] = false;
|
||||
JudgeMiss();
|
||||
yield break; // 停止检查
|
||||
}
|
||||
Debug.Log($"[HoldNote] End段超时未松手,判定为Miss: {noteColor}");
|
||||
HandleEnd(true); // 传入true表示强制Miss
|
||||
isJudged = true;
|
||||
}
|
||||
yield return new WaitForSeconds(KEY_CHECK_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D collision)
|
||||
{
|
||||
if (!collision.CompareTag("JudgmentLine")) return;
|
||||
if (!IsHoldNoteValid()) return;
|
||||
|
||||
if (segment == NoteSegment.Start || segment == NoteSegment.Middle)
|
||||
{
|
||||
hasEnteredLine = true;
|
||||
}
|
||||
hasEnteredLine = true; // 只要进入判定线,就标记
|
||||
|
||||
if (segment == NoteSegment.Middle)
|
||||
{
|
||||
//Debug.Log($"[HoldNote] Middle段进入判定线: color={noteColor}, isStartJudged={JudgeManager.Instance.IsStartJudged(noteID)}, hasReleased={JudgeManager.Instance.HasNoteReleased(noteID)}, isKeyDown={Input.GetKey(keyToPress)}");
|
||||
// 进入判定区时记录是否已按住
|
||||
hasBeenHeldFromStart = Input.GetKey(keyToPress);
|
||||
Debug.Log($"[HoldNote] Middle段进入判定线: color={noteColor}, isStartJudged={JudgeManager.Instance.IsStartJudged(noteID)}, hasReleased={JudgeManager.Instance.HasNoteReleased(noteID)}, isKeyHeld={Input.GetKey(keyToPress)}, hasBeenHeldFromStart={hasBeenHeldFromStart}");
|
||||
|
||||
if (autoReturnCoroutine != null)
|
||||
StopCoroutine(autoReturnCoroutine);
|
||||
|
||||
autoReturnCoroutine = StartCoroutine(DelayedAutoReturnCheck());
|
||||
// Middle段的延迟回收主要确保即使按住也能在适当时间回收
|
||||
// 重新思考这里的必要性,可能让Update和OnTriggerExit2D处理更直接
|
||||
// 暂时移除这里的 DelayedAutoReturnCheck,让 Update 和 OnTriggerExit2D 主导 Middle 段的生命周期
|
||||
// autoReturnCoroutine = StartCoroutine(DelayedAutoReturnCheck());
|
||||
}
|
||||
|
||||
if (segment == NoteSegment.End)
|
||||
else if (segment == NoteSegment.End)
|
||||
{
|
||||
if (autoReturnCoroutine != null)
|
||||
StopCoroutine(autoReturnCoroutine);
|
||||
|
||||
// End段的延迟回收主要用于处理玩家是否松手,以及超时回收
|
||||
autoReturnCoroutine = StartCoroutine(DelayedAutoReturnCheck());
|
||||
}
|
||||
}
|
||||
@@ -196,27 +221,45 @@ public class HoldNote : BaseNote
|
||||
private void OnTriggerExit2D(Collider2D collision)
|
||||
{
|
||||
if (!collision.CompareTag("JudgmentLine")) return;
|
||||
if (!IsHoldNoteValid()) return;
|
||||
|
||||
// Start段离开判定线时如果未被判定,则视为Miss并回收
|
||||
if (segment == NoteSegment.Start)
|
||||
{
|
||||
if (!JudgeManager.Instance.IsStartJudged(noteID))
|
||||
{
|
||||
Debug.Log($"[HoldNote] Start段离开判定线未判定,强制回收 (Miss): {noteColor}");
|
||||
// 可以触发一个Miss判定
|
||||
// JudgeManager.Instance.RecordMiss(noteID);
|
||||
ReturnToPool();
|
||||
}
|
||||
// 如果Start段已判定,那么它会通过DelayedReturn被回收,这里不做额外处理
|
||||
}
|
||||
if (segment == NoteSegment.Middle)
|
||||
// Middle段离开判定线时,如果头部未判定,或者长按已经失效(松手),则回收
|
||||
else if (segment == NoteSegment.Middle) // 这里修正了拼写错误
|
||||
{
|
||||
if (!JudgeManager.Instance.IsStartJudged(noteID) ||
|
||||
JudgeManager.Instance.HasNoteReleased(noteID))
|
||||
{
|
||||
//Debug.Log($"[HoldNote] Middle段离开判定线强制回收: {noteColor}");
|
||||
ReturnToPool();
|
||||
}
|
||||
// 无论Start段是否判定,如果Middle段离开了判定线,就应该回收
|
||||
// 因为它已经完成了它的“通过”或者“错过”的职责
|
||||
Debug.Log($"[HoldNote] Middle段离开判定线强制回收: {noteColor}");
|
||||
ReturnToPool();
|
||||
}
|
||||
// End段离开判定线时,如果头部未判定,或者长按已经失效,或者 End 段已经完成判定,则回收
|
||||
else if (segment == NoteSegment.End)
|
||||
{
|
||||
if (!JudgeManager.Instance.IsStartJudged(noteID) || JudgeManager.Instance.HasNoteReleased(noteID))
|
||||
if (!isJudged) // 如果End段还没有被判定
|
||||
{
|
||||
// 如果头部没判,或者长按中断,或者玩家按得过久(已经过了scheduledEndTime很多了)
|
||||
if (!JudgeManager.Instance.IsStartJudged(noteID) || !isHoldActive || Time.time > scheduledEndTime + 0.15f)
|
||||
{
|
||||
Debug.Log($"[HoldNote] End段离开判定线强制回收 (头部未判定或长按中断或超时): {noteColor}");
|
||||
// 可以在这里强制一个Miss判定,如果之前没触发过
|
||||
if (!JudgeManager.Instance.HasNoteReleased(noteID)) // 确保没有重复判定
|
||||
{
|
||||
HandleEnd(true); // 传入true表示强制Miss
|
||||
}
|
||||
ReturnToPool();
|
||||
}
|
||||
}
|
||||
else // 如果End段已经被判定过了,直接回收
|
||||
{
|
||||
ReturnToPool();
|
||||
}
|
||||
@@ -225,139 +268,110 @@ public class HoldNote : BaseNote
|
||||
|
||||
private IEnumerator DelayedAutoReturnCheck()
|
||||
{
|
||||
// 这里的 DelayedAutoReturnCheck 主要是针对 End 段的超时未松手检查
|
||||
// 或者 Start 段的延迟补判(这部分已经移到 Update 和 HandleStart)
|
||||
yield return null;
|
||||
|
||||
if (!IsHoldNoteValid()) yield break;
|
||||
|
||||
bool isStartJudgedBefore = JudgeManager.Instance.IsStartJudged(noteID);
|
||||
bool isKeyHeldNow = Input.GetKey(keyToPress);
|
||||
|
||||
//Debug.Log($"[HoldNote] 延迟检查开始: noteID={noteID}, color={noteColor}, isStartJudged={isStartJudgedBefore}, isKeyHeld={isKeyHeldNow}");
|
||||
|
||||
if (!isStartJudgedBefore && isKeyHeldNow)
|
||||
if (segment == NoteSegment.End)
|
||||
{
|
||||
//Debug.Log($"[HoldNote] 延迟回补 Start 判定触发: {noteColor}");
|
||||
HandleStart();
|
||||
}
|
||||
|
||||
bool isStartJudged = JudgeManager.Instance.IsStartJudged(noteID);
|
||||
bool hasReleased = JudgeManager.Instance.HasNoteReleased(noteID);
|
||||
bool isKeyHeld = Input.GetKey(keyToPress);
|
||||
|
||||
//Debug.Log($"[HoldNote] 延迟确认状态: isStartJudged={isStartJudged}, hasReleased={hasReleased}, isKeyHeld={isKeyHeld}");
|
||||
|
||||
if (segment == NoteSegment.Middle && isStartJudged && isKeyHeld)
|
||||
{
|
||||
float waitTime = Mathf.Max(0f, hitTime - Time.time);
|
||||
//Debug.Log($"[HoldNote] Middle段通过延迟确认,准备等待到 hitTime 回收: {noteColor}, 剩余时间={waitTime:F2}s");
|
||||
|
||||
yield return new WaitForSeconds(waitTime);
|
||||
|
||||
if (Input.GetKey(keyToPress) && !JudgeManager.Instance.HasNoteReleased(noteID))
|
||||
// 在这里等待,看玩家是否在 scheduledEndTime 之前松手
|
||||
// 如果到达 scheduledEndTime 玩家仍未松手,且长按有效,则继续等待
|
||||
// 如果玩家按得过长,可以在 Update 中触发 Miss 判定
|
||||
float timeToWait = Mathf.Max(0f, scheduledEndTime - Time.time);
|
||||
if (timeToWait > 0f)
|
||||
{
|
||||
//Debug.Log($"[HoldNote] Middle段在 hitTime 达成条件,回收: {noteColor}");
|
||||
ReturnToPool();
|
||||
yield return new WaitForSeconds(timeToWait);
|
||||
}
|
||||
else
|
||||
|
||||
// 到达 scheduledEndTime 后再次检查
|
||||
// 如果此时玩家仍在按住,并且 Start 段已判定,但 End 段还没被判定释放
|
||||
// 说明玩家按得太久,判定为 Miss
|
||||
if (!isJudged && JudgeManager.Instance.IsStartJudged(noteID) && Input.GetKey(keyToPress))
|
||||
{
|
||||
//Debug.Log($"[HoldNote] Middle段在 hitTime 条件不符,不回收: {noteColor}");
|
||||
Debug.Log($"[HoldNote] DelayedAutoReturnCheck (End段) 判定:玩家按得过长,Miss! {noteColor}");
|
||||
HandleEnd(true); // 强制 Miss
|
||||
}
|
||||
}
|
||||
else if (segment == NoteSegment.End && !hasReleased)
|
||||
{
|
||||
//Debug.Log($"[HoldNote] End段延迟检查后仍未释放: {noteColor}");
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.Log($"[HoldNote] DelayedAutoReturnCheck 无需处理: segment={segment}, color={noteColor}");
|
||||
// 无论如何,如果 End 段已经过去了,但还没回收,就回收
|
||||
if (!gameObject.activeSelf)
|
||||
{
|
||||
yield break; // 修正!使用 yield break 来结束协程
|
||||
}
|
||||
ReturnToPool();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleStart()
|
||||
{
|
||||
if (!IsHoldNoteValid()) return;
|
||||
|
||||
float pressTime = Time.time;
|
||||
float offset = Mathf.Abs(pressTime - hitTime);
|
||||
|
||||
if (offset < startHitWindow)
|
||||
if (offset < 0.1f) // 判定成功
|
||||
{
|
||||
//Debug.Log($"[HoldNote] START判定成功(偏差={offset:F2}秒): {noteColor}");
|
||||
Debug.Log($"[HoldNote] START判定成功(偏差={offset:F2}秒): {noteColor}");
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||||
JudgeManager.Instance.RegisterHoldNoteStart(noteID, Time.time);
|
||||
|
||||
// 开始按键按住检测
|
||||
_isKeyHeld = true;
|
||||
_lastKeyPressTime = Time.time;
|
||||
if (_keyCheckCoroutine != null)
|
||||
StopCoroutine(_keyCheckCoroutine);
|
||||
_keyCheckCoroutine = StartCoroutine(CheckKeyHold());
|
||||
|
||||
hasEnteredLine = true;
|
||||
StartCoroutine(DelayedReturn());
|
||||
isHoldActive = true; // 成功判定Start,长按状态激活
|
||||
PlayHitAnimation();
|
||||
}
|
||||
else
|
||||
{
|
||||
_holdNoteValidity[id] = false;
|
||||
//Debug.Log($"[HoldNote] START Miss(偏差={offset:F2}秒,窗口={startHitWindow:F2}秒): {noteColor}");
|
||||
JudgeMiss();
|
||||
Debug.Log($"[HoldNote] START Miss(偏差={offset:F2}秒): {noteColor}");
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, false); // 明确标记未判定
|
||||
isHoldActive = false; // Start Miss,长按状态不激活
|
||||
}
|
||||
}
|
||||
|
||||
private void JudgeMiss()
|
||||
{
|
||||
// 执行Miss判定逻辑
|
||||
//Debug.Log($"[HoldNote] 判定为Miss: {noteColor}");
|
||||
ReturnToPool();
|
||||
// Start段判定后立即回收,因为它的视觉部分可能只需要短暂显示
|
||||
StartCoroutine(DelayedReturn());
|
||||
}
|
||||
|
||||
private IEnumerator DelayedReturn()
|
||||
{
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
ReturnToPool();
|
||||
yield return new WaitForSeconds(0.05f); // 短暂延迟,确保视觉效果或数据更新
|
||||
if (gameObject.activeSelf) // 确保对象还活跃,防止重复回收
|
||||
{
|
||||
ReturnToPool();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEnd()
|
||||
// 重载 HandleEnd 方法,增加一个强制Miss的参数
|
||||
private void HandleEnd(bool forceMiss = false)
|
||||
{
|
||||
if (!IsHoldNoteValid()) return;
|
||||
if (!JudgeManager.Instance.IsStartJudged(noteID))
|
||||
{
|
||||
Debug.Log($"[HoldNote] 结束段判定失败:起始段未正确判定");
|
||||
return;
|
||||
}
|
||||
|
||||
releaseTime = Time.time;
|
||||
hasReleased = true;
|
||||
float diff = Mathf.Abs(releaseTime - scheduledEndTime);
|
||||
string result = "Miss";
|
||||
hasReleased = true; // 标记此音符已被释放
|
||||
|
||||
if (diff < endHitWindow)
|
||||
JudgeManager.Instance.RegisterNoteReleased(noteID, true); // 在 JudgeManager 中注册释放
|
||||
|
||||
string result;
|
||||
|
||||
// 如果强制Miss,或者头部未判定,或者长按已经中断,则直接判定为Miss
|
||||
if (forceMiss || !JudgeManager.Instance.IsStartJudged(noteID) || !isHoldActive)
|
||||
{
|
||||
result = "Perfect";
|
||||
JudgeManager.Instance.RegisterNoteReleased(noteID, true);
|
||||
ReturnToPool();
|
||||
result = "Miss";
|
||||
Debug.LogWarning($"[HoldNote] END判定失败({noteColor}):{(forceMiss ? "强制Miss" : (!JudgeManager.Instance.IsStartJudged(noteID) ? "头部未判定" : "长按中断"))}");
|
||||
}
|
||||
else if (diff < endHitWindow * 2f)
|
||||
else
|
||||
{
|
||||
result = "Good";
|
||||
JudgeManager.Instance.RegisterNoteReleased(noteID, true);
|
||||
ReturnToPool();
|
||||
float diff = Mathf.Abs(releaseTime - scheduledEndTime);
|
||||
// End段的判定规则
|
||||
if (diff <= 0.05f) // 更严格的完美判定窗口
|
||||
{
|
||||
result = "Perfect";
|
||||
}
|
||||
else if (diff < 0.15f) // 适当放宽的Great判定
|
||||
{
|
||||
result = "Great";
|
||||
}
|
||||
else if (diff < 0.3f) // 再次放宽的Good判定
|
||||
{
|
||||
result = "Good";
|
||||
}
|
||||
else
|
||||
{
|
||||
result = "Bad"; // 更大的偏差可能视为Bad或Miss
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"[HoldNote] END判定结果: {noteColor} {result} (release={releaseTime:F2}, target={scheduledEndTime:F2})");
|
||||
}
|
||||
|
||||
private void OnTriggerStay2D(Collider2D collision)
|
||||
{
|
||||
if (!collision.CompareTag("JudgmentLine")) return;
|
||||
if (!IsHoldNoteValid()) return;
|
||||
|
||||
if (segment == NoteSegment.Middle &&
|
||||
JudgeManager.Instance.IsStartJudged(noteID) &&
|
||||
Input.GetKey(keyToPress))
|
||||
{
|
||||
hasEnteredLine = true;
|
||||
}
|
||||
ReturnToPool(); // 判定后回收
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
@@ -368,21 +382,27 @@ public class HoldNote : BaseNote
|
||||
autoReturnCoroutine = null;
|
||||
}
|
||||
|
||||
if (_keyCheckCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_keyCheckCoroutine);
|
||||
_keyCheckCoroutine = null;
|
||||
}
|
||||
|
||||
// OnDisable时对End段的补偿处理:
|
||||
// 如果是End段,且尚未被判定,且Start段已判定,但此时按键已经松开 (通过 !Input.GetKey(keyToPress) 判断)
|
||||
// 这意味着玩家在离开判定区或因其他原因导致 OnDisable 时松手,但未触发正常的 HandleEnd
|
||||
if (segment == NoteSegment.End &&
|
||||
!hasReleased &&
|
||||
!JudgeManager.Instance.HasNoteReleased(noteID) &&
|
||||
keyToPress != KeyCode.None &&
|
||||
!Input.GetKey(keyToPress))
|
||||
!isJudged && // 尚未判定
|
||||
JudgeManager.Instance.IsStartJudged(noteID)) // 头部已判定
|
||||
{
|
||||
hasReleased = true;
|
||||
JudgeManager.Instance.RegisterNoteReleased(noteID, true);
|
||||
//Debug.Log($"[HoldNote] OnDisable补偿KeyUp: {noteColor}");
|
||||
// 如果按键已经松开,或者长按状态已经失效 (可能因为松开过快)
|
||||
if (!Input.GetKey(keyToPress) || !isHoldActive)
|
||||
{
|
||||
Debug.Log($"[HoldNote] OnDisable补偿End段Miss判定: {noteColor}");
|
||||
HandleEnd(true); // 强制Miss
|
||||
isJudged = true; // 标记已判定
|
||||
}
|
||||
else if (Input.GetKey(keyToPress))
|
||||
{
|
||||
// 如果End段离开了判定区,但是玩家还在按住,这属于按得过长
|
||||
Debug.Log($"[HoldNote] OnDisable:End段在离开判定区时玩家仍未松手,判定为Miss: {noteColor}");
|
||||
HandleEnd(true); // 强制Miss
|
||||
isJudged = true;
|
||||
}
|
||||
}
|
||||
|
||||
controller?.StopMovement();
|
||||
@@ -390,11 +410,11 @@ public class HoldNote : BaseNote
|
||||
|
||||
private void ReturnToPool()
|
||||
{
|
||||
if (!gameObject.activeSelf) return;
|
||||
if (!gameObject.activeSelf) return; // 再次检查以防止对象已被禁用
|
||||
|
||||
if (segment == NoteSegment.Start && !hasEnteredLine)
|
||||
{
|
||||
//Debug.LogWarning($"[HoldNote] 阻止回收尚未进入判定线的 Start段: {noteColor}");
|
||||
Debug.LogWarning($"[HoldNote] 阻止回收尚未进入判定线的 Start段: {noteColor}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -416,8 +436,10 @@ public class HoldNote : BaseNote
|
||||
hitTime = 0f;
|
||||
scheduledEndTime = 0f;
|
||||
hasEnteredLine = false;
|
||||
_isKeyHeld = false;
|
||||
_lastKeyPressTime = 0f;
|
||||
isJudged = false;
|
||||
isHoldActive = false; // 重置长按标记
|
||||
hasBeenHeldFromStart = false; // 重置
|
||||
// canJudgeThisSegment = false; // 此变量已被移除或不再关键
|
||||
|
||||
if (autoReturnCoroutine != null)
|
||||
{
|
||||
@@ -425,20 +447,6 @@ public class HoldNote : BaseNote
|
||||
autoReturnCoroutine = null;
|
||||
}
|
||||
|
||||
if (_keyCheckCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_keyCheckCoroutine);
|
||||
_keyCheckCoroutine = null;
|
||||
}
|
||||
|
||||
controller?.StopMovement();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (segment == NoteSegment.End && _holdNoteValidity.ContainsKey(id))
|
||||
{
|
||||
_holdNoteValidity.Remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86977693d5a2d364bb086303f60ca1b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 86977693d5a2d364bb086303f60ca1b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -6,15 +6,8 @@ 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);
|
||||
@@ -22,31 +15,12 @@ public class InputManager : MonoBehaviour
|
||||
|
||||
if (Input.GetKeyDown(key))
|
||||
{
|
||||
// 记录当前帧按下的所有按键
|
||||
pressedKeysThisFrame[pressedKeyCount++] = key;
|
||||
OnKeyPressed?.Invoke(key);
|
||||
// 调用 JudgeManager 统一判断最早的音符
|
||||
JudgeManager.Instance.JudgeEarliestNote(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2c4f957042317641bf529c141c39b91
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: b2c4f957042317641bf529c141c39b91
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -8,42 +8,9 @@ public class JudgeManager : MonoBehaviour
|
||||
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>();
|
||||
// 记录 End 段的 scheduledEndTime(防止 End 被回收后仍然能判定)
|
||||
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()
|
||||
{
|
||||
@@ -58,68 +25,25 @@ public class JudgeManager : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
#region 长音符状态管理
|
||||
public bool IsHoldNoteValid(int holdNoteId)
|
||||
/// <summary>
|
||||
/// 记录某个颜色的长音符 End 段的 scheduledEndTime
|
||||
/// </summary>
|
||||
public void RegisterScheduledEndTime(string noteColor, float endTime)
|
||||
{
|
||||
return _holdNoteValidity.ContainsKey(holdNoteId) && _holdNoteValidity[holdNoteId];
|
||||
noteEndTimes[noteColor] = endTime;
|
||||
}
|
||||
|
||||
public void SetHoldNoteValidity(int holdNoteId, bool isValid)
|
||||
/// <summary>
|
||||
/// 获取某个颜色的 End 段的 scheduledEndTime
|
||||
/// </summary>
|
||||
public float GetScheduledEndTime(string noteColor)
|
||||
{
|
||||
_holdNoteValidity[holdNoteId] = isValid;
|
||||
//Debug.Log($"设置长音符有效性: ID={holdNoteId}, Valid={isValid}");
|
||||
return noteEndTimes.ContainsKey(noteColor) ? noteEndTimes[noteColor] : 0f;
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -130,16 +54,17 @@ public class JudgeManager : MonoBehaviour
|
||||
public void RegisterNoteReleased(string noteID, bool state)
|
||||
{
|
||||
releasedNotes[noteID] = state;
|
||||
//Debug.Log($"[JudgeManager] RegisterNoteReleased: {noteID} = {state}");
|
||||
Debug.Log($"[JudgeManager] RegisterNoteReleased: {noteID} = {state}");
|
||||
}
|
||||
|
||||
public bool HasNoteReleased(string noteID)
|
||||
{
|
||||
return releasedNotes.ContainsKey(noteID) && releasedNotes[noteID];
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 短音符判定
|
||||
/// <summary>
|
||||
/// 当音符进入判定区域时调用
|
||||
/// </summary>
|
||||
public void RegisterNote(KeyCode key, Note note)
|
||||
{
|
||||
if (!judgeQueues.ContainsKey(key))
|
||||
@@ -151,6 +76,9 @@ public class JudgeManager : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当音符离开判定区域时调用
|
||||
/// </summary>
|
||||
public void UnregisterNote(KeyCode key, Note note)
|
||||
{
|
||||
if (!judgeQueues.ContainsKey(key)) return;
|
||||
@@ -163,34 +91,32 @@ public class JudgeManager : MonoBehaviour
|
||||
{
|
||||
newQueue.Enqueue(n);
|
||||
}
|
||||
else if (!n.IsJudged())
|
||||
else
|
||||
{
|
||||
n.JudgeMiss();
|
||||
// **如果音符未被判定,则触发 Miss**
|
||||
if (!n.IsJudged())
|
||||
{
|
||||
n.JudgeMiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
judgeQueues[key] = newQueue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按键按下时,判定队列中最早进入的音符
|
||||
/// </summary>
|
||||
public void JudgeEarliestNote(KeyCode key)
|
||||
{
|
||||
if (judgeQueues.ContainsKey(key) && judgeQueues[key].Count > 0)
|
||||
{
|
||||
Note note = judgeQueues[key].Dequeue();
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e726455158177b3499eb8f0910c54e30
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: e726455158177b3499eb8f0910c54e30
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -7,7 +7,7 @@ public class KeyBindingManager : MonoBehaviour
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
//Debug.Log("KeyBindingManager Awake() 被调用,开始加载按键映射...");
|
||||
Debug.Log("KeyBindingManager Awake() 被调用,开始加载按键映射...");
|
||||
|
||||
if (keyBindings.Count == 0)
|
||||
{
|
||||
@@ -15,18 +15,18 @@ public class KeyBindingManager : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> 获取颜色对应的按键 </summary>
|
||||
/// <summary> 获取颜色对应的按键 </summary>
|
||||
public static KeyCode GetKeyForColor(string color)
|
||||
{
|
||||
if (keyBindings.TryGetValue(color.ToLower(), out KeyCode key))
|
||||
{
|
||||
return key;
|
||||
}
|
||||
//Debug.LogError($"未找到颜色 {color} 对应的按键!请检查 KeyBindingManager 是否正确初始化。");
|
||||
Debug.LogError($"未找到颜色 {color} 对应的按键!请检查 KeyBindingManager 是否正确初始化。");
|
||||
return KeyCode.None;
|
||||
}
|
||||
|
||||
/// <summary> 修改按键绑定 </summary>
|
||||
/// <summary> 修改按键绑定 </summary>
|
||||
public static void ChangeKeyBinding(string color, KeyCode newKey)
|
||||
{
|
||||
if (keyBindings.ContainsKey(color.ToLower()))
|
||||
@@ -41,7 +41,7 @@ public class KeyBindingManager : MonoBehaviour
|
||||
SaveKeyBindings();
|
||||
}
|
||||
|
||||
/// <summary> 存储按键绑定到 `PlayerPrefs` </summary>
|
||||
/// <summary> 存储按键绑定到 `PlayerPrefs` </summary>
|
||||
private static void SaveKeyBindings()
|
||||
{
|
||||
foreach (var kvp in keyBindings)
|
||||
@@ -51,7 +51,7 @@ public class KeyBindingManager : MonoBehaviour
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
/// <summary> 从 `PlayerPrefs` 加载按键绑定 </summary>
|
||||
/// <summary> 从 `PlayerPrefs` 加载按键绑定 </summary>
|
||||
private static void LoadKeyBindings()
|
||||
{
|
||||
string[] colors = { "red", "green", "yellow", "purple", "blue" };
|
||||
@@ -69,6 +69,6 @@ public class KeyBindingManager : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
//Debug.Log("KeyBindings 初始化成功:" + string.Join(", ", keyBindings));
|
||||
Debug.Log("KeyBindings 初始化成功:" + string.Join(", ", keyBindings));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2639b2e07c4995842bdbe955d9fb6bfb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 2639b2e07c4995842bdbe955d9fb6bfb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -59,28 +59,28 @@ public class Notes : MonoBehaviour
|
||||
|
||||
private void JudgePerfect()
|
||||
{
|
||||
//Debug.Log($"Track {trackIndex}: Perfect!");
|
||||
Debug.Log($"Track {trackIndex}: Perfect!");
|
||||
isHit = true;
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void JudgeGreat()
|
||||
{
|
||||
//Debug.Log($"Track {trackIndex}: Great!");
|
||||
Debug.Log($"Track {trackIndex}: Great!");
|
||||
isHit = true;
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void JudgeGood()
|
||||
{
|
||||
//Debug.Log($"Track {trackIndex}: Good!");
|
||||
Debug.Log($"Track {trackIndex}: Good!");
|
||||
isHit = true;
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void JudgeMiss()
|
||||
{
|
||||
//Debug.Log($"Track {trackIndex}: Miss!");
|
||||
Debug.Log($"Track {trackIndex}: Miss!");
|
||||
isHit = true;
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d8062cc45ed8f84da8460fcd2964b77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 8d8062cc45ed8f84da8460fcd2964b77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -28,15 +28,12 @@ public class Note : BaseNote
|
||||
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)
|
||||
@@ -49,15 +46,15 @@ public class Note : BaseNote
|
||||
|
||||
if (timeDifference <= 0.08f)
|
||||
{
|
||||
//Debug.Log($"短音符 {keyToPress}: Perfect");
|
||||
Debug.Log($"短音符 {keyToPress}: Perfect");
|
||||
}
|
||||
else if (timeDifference <= 0.15f)
|
||||
{
|
||||
//Debug.Log($"短音符 {keyToPress}: Great");
|
||||
Debug.Log($"短音符 {keyToPress}: Great");
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.Log($"短音符 {keyToPress} Miss");
|
||||
Debug.Log($"短音符 {keyToPress} Miss");
|
||||
JudgeMiss();
|
||||
return;
|
||||
}
|
||||
@@ -84,7 +81,7 @@ public class Note : BaseNote
|
||||
ReturnToPool();
|
||||
if (anim !=null)
|
||||
{
|
||||
//Debug.Log("执行");
|
||||
Debug.Log("执行");
|
||||
anim.PlayDestroyAnimation(noteColor);
|
||||
}
|
||||
|
||||
@@ -94,7 +91,7 @@ public class Note : BaseNote
|
||||
{
|
||||
if (isJudged) return;
|
||||
isJudged = true;
|
||||
//Debug.Log($"短音符 {keyToPress} Miss");
|
||||
Debug.Log($"短音符 {keyToPress} Miss");
|
||||
ReturnToPool();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7d5a4ca6fc6b784d89e910ff90c2c7a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: a7d5a4ca6fc6b784d89e910ff90c2c7a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -5,7 +5,7 @@ public class NoteController : MonoBehaviour
|
||||
{
|
||||
private float speed;
|
||||
private bool isMoving = true;
|
||||
private bool isInJudgeZone = false; // 判定区域标记
|
||||
private bool isInJudgeZone = false; // 判定区域标记
|
||||
|
||||
private ParticleSystem hitEffect;
|
||||
private Note linkedNote;
|
||||
@@ -59,7 +59,7 @@ public class NoteController : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
// 将 OnTriggerEnter2D 和 OnTriggerExit2D 挂载在 Controller 上
|
||||
// 将 OnTriggerEnter2D 和 OnTriggerExit2D 挂载在 Controller 上
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D collision)
|
||||
{
|
||||
@@ -92,7 +92,7 @@ public class NoteController : MonoBehaviour
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 供 Note 查询是否处于判定区域
|
||||
/// 供 Note 查询是否处于判定区域
|
||||
/// </summary>
|
||||
public bool IsInJudgeZone()
|
||||
{
|
||||
|
||||
@@ -68,7 +68,7 @@ public class NotePool : MonoBehaviour
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.LogWarning("对象池已满,生成新的音符!");
|
||||
Debug.LogWarning("对象池已满,生成新的音符!");
|
||||
GameObject obj = Instantiate(prefab);
|
||||
obj.SetActive(true);
|
||||
return obj;
|
||||
@@ -86,11 +86,11 @@ public class NotePool : MonoBehaviour
|
||||
if (pool.Count < maxPoolSize)
|
||||
{
|
||||
pool.Push(obj);
|
||||
//Debug.Log($"已归还对象:{obj.name},当前池容量:{pool.Count}");
|
||||
Debug.Log($"已归还对象:{obj.name},当前池容量:{pool.Count}");
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.LogWarning("对象池已满,无法继续归还对象!");
|
||||
Debug.LogWarning("对象池已满,无法继续归还对象!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,18 +128,12 @@ public class NotePool : MonoBehaviour
|
||||
|
||||
public void ReturnNote(GameObject note, string color)
|
||||
{
|
||||
Note noteComponent = note.GetComponent<Note>();
|
||||
if (noteComponent != null)
|
||||
|
||||
NoteController noteScript = note.GetComponent<NoteController>();
|
||||
if (noteScript != null)
|
||||
{
|
||||
JudgeManager.Instance?.UnregisterNote(noteComponent.GetKey(), noteComponent);
|
||||
noteScript.ResetState();
|
||||
}
|
||||
|
||||
NoteController noteController = note.GetComponent<NoteController>();
|
||||
if (noteController != null)
|
||||
{
|
||||
noteController.ResetState();
|
||||
}
|
||||
|
||||
ReturnObjectToPool(notePools[GetColorIndexFromName(color)], note);
|
||||
}
|
||||
|
||||
@@ -147,7 +141,7 @@ public class NotePool : MonoBehaviour
|
||||
{
|
||||
if (string.IsNullOrEmpty(color))
|
||||
{
|
||||
//Debug.LogWarning($" 归还 StartNote 时 color 为空!音符名: {startNote.name}");
|
||||
Debug.LogWarning($" 归还 StartNote 时 color 为空!音符名: {startNote.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -164,7 +158,7 @@ public class NotePool : MonoBehaviour
|
||||
{
|
||||
if (string.IsNullOrEmpty(color))
|
||||
{
|
||||
//Debug.LogWarning($" 归还 HoldNoteSegment 时 color 为空!音符名: {holdNote.name}");
|
||||
Debug.LogWarning($" 归还 HoldNoteSegment 时 color 为空!音符名: {holdNote.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -189,7 +183,7 @@ public class NotePool : MonoBehaviour
|
||||
case "purple": return 3;
|
||||
case "blue": return 4;
|
||||
default:
|
||||
//Debug.LogError($"未识别的颜色: {colorName},默认使用红色");
|
||||
Debug.LogError($"未识别的颜色: {colorName},默认使用红色");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f4db5764af5850449f3b6314af7fb46
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 9f4db5764af5850449f3b6314af7fb46
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using Unity.VisualScripting;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using TMPro;
|
||||
|
||||
public class NoteSpawner : MonoBehaviour
|
||||
@@ -18,19 +20,11 @@ public class NoteSpawner : MonoBehaviour
|
||||
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>();
|
||||
}
|
||||
}
|
||||
private static int holdNoteIdCounter = 0; // 全局唯一长音符 ID 计数器
|
||||
|
||||
public void LoadBeatmap(Beatmap loadedBeatmap)
|
||||
{
|
||||
@@ -40,13 +34,13 @@ public class NoteSpawner : MonoBehaviour
|
||||
beatmap = loadedBeatmap;
|
||||
if (beatmap == null)
|
||||
{
|
||||
//Debug.LogError("加载的谱面为空!");
|
||||
Debug.LogError("加载的谱面为空!");
|
||||
return;
|
||||
}
|
||||
|
||||
bpm = beatmap.bpm;
|
||||
startTime = Time.time;
|
||||
//Debug.Log($"歌曲开始时间: {startTime}");
|
||||
Debug.Log($"歌曲开始时间: {startTime}");
|
||||
StartCoroutine(SpawnNotes());
|
||||
}
|
||||
|
||||
@@ -54,7 +48,7 @@ public class NoteSpawner : MonoBehaviour
|
||||
{
|
||||
if (beatmap == null || beatmap.notes == null)
|
||||
{
|
||||
//Debug.LogError("谱面数据为空!");
|
||||
Debug.LogError("谱面数据为空!");
|
||||
isSpawning = false;
|
||||
yield break;
|
||||
}
|
||||
@@ -87,23 +81,23 @@ public class NoteSpawner : MonoBehaviour
|
||||
{
|
||||
if (noteData.trackIndex < 0 || noteData.trackIndex >= spawnPoints.Length)
|
||||
{
|
||||
//Debug.LogError("轨道索引超出范围!");
|
||||
Debug.LogError("轨道索引超出范围!");
|
||||
return;
|
||||
}
|
||||
|
||||
KeyCode key = KeyBindingManager.GetKeyForColor(noteData.color);
|
||||
if (key == KeyCode.None)
|
||||
{
|
||||
//Debug.LogError($"未找到颜色 {noteData.color} 对应的按键!请检查 KeyBindingManager 是否正确初始化。");
|
||||
Debug.LogError($"未找到颜色 {noteData.color} 对应的按键!请检查 KeyBindingManager 是否正确初始化。");
|
||||
return;
|
||||
}
|
||||
|
||||
//Debug.Log($"生成短音符:颜色={noteData.color}, 按键={key}, 轨道={noteData.trackIndex}");
|
||||
Debug.Log($"生成短音符:颜色={noteData.color}, 按键={key}, 轨道={noteData.trackIndex}");
|
||||
|
||||
GameObject note = notePool.GetNote(noteData.color);
|
||||
if (note == null)
|
||||
{
|
||||
//Debug.LogError("对象池返回了一个空音符!");
|
||||
Debug.LogError("对象池返回了一个空音符!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,61 +109,66 @@ public class NoteSpawner : MonoBehaviour
|
||||
{
|
||||
float noteSpawnTime = Time.time;
|
||||
noteScript.Setup(key, noteData.trackIndex, CalculateSpeed(), noteData.time, noteData.color);
|
||||
//Debug.Log($"到达时间:{noteSpawnTime + (60f / bpm) * 4}");
|
||||
Debug.Log($"到达时间:{noteSpawnTime + (60f / bpm) * 4}");
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.LogError("音符预制体缺少 Note 组件!");
|
||||
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("轨道索引超出范围!");
|
||||
Debug.LogError("轨道索引超出范围!");
|
||||
return;
|
||||
}
|
||||
|
||||
KeyCode key = KeyBindingManager.GetKeyForColor(noteData.color);
|
||||
if (key == KeyCode.None)
|
||||
{
|
||||
//Debug.LogError($"未找到颜色 {noteData.color} 对应的按键!");
|
||||
Debug.LogError($"未找到颜色 {noteData.color} 对应的按键!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(noteData.color))
|
||||
{
|
||||
//Debug.LogError("[NoteSpawner] noteData.color 为空,无法生成音符!");
|
||||
Debug.LogError("[NoteSpawner] noteData.color 为空,无法生成音符!");
|
||||
return;
|
||||
}
|
||||
|
||||
//Debug.Log($"生成时间:{Time.time}");
|
||||
//Debug.Log($"生成长音符:颜色={noteData.color}, 按键={key}, 轨道={noteData.trackIndex}");
|
||||
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();
|
||||
// 生成唯一 id
|
||||
int holdNoteId = Random.Range(100000, 999999);
|
||||
|
||||
// 生成 Start 段
|
||||
GameObject startObj = notePool.GetStartNote(noteData.color);
|
||||
if (startObj == null)
|
||||
{
|
||||
//Debug.LogError("对象池返回空 hold note start!");
|
||||
Debug.LogError("对象池返回空 hold note start!");
|
||||
return;
|
||||
}
|
||||
startObj.transform.position = spawnPoint.position;
|
||||
startObj.transform.rotation = Quaternion.identity;
|
||||
|
||||
SetupHoldNoteSegment(startObj, spawnPoint, holdNoteId, noteData, key, 0f, false, "start", scheduledEndTime);
|
||||
HoldNote holdNote = startObj.GetComponent<HoldNote>();
|
||||
if (holdNote != null)
|
||||
{
|
||||
holdNote.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), noteData.time, 0f, false, scheduledEndTime, noteData.color, key, "start");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("长音符 start 部分缺少 HoldNote 组件!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成 Middle 和 End 段
|
||||
for (int i = 1; i < segmentCount; i++)
|
||||
@@ -178,9 +177,6 @@ public class NoteSpawner : MonoBehaviour
|
||||
bool isEndSegment = (i == segmentCount - 1);
|
||||
string partType = isEndSegment ? "end" : "middle";
|
||||
|
||||
// 应用gracePeriod
|
||||
segmentDelay = CalculateSegmentDelay(segmentDelay, isEndSegment);
|
||||
|
||||
GameObject segObj = notePool.GetHoldNoteSegment(noteData.color);
|
||||
if (segObj == null)
|
||||
{
|
||||
@@ -188,60 +184,34 @@ public class NoteSpawner : MonoBehaviour
|
||||
continue;
|
||||
}
|
||||
|
||||
SetupHoldNoteSegment(segObj, spawnPoint, holdNoteId, noteData, key, segmentDelay, isEndSegment, partType, scheduledEndTime);
|
||||
}
|
||||
}
|
||||
segObj.transform.position = spawnPoint.position;
|
||||
segObj.transform.rotation = Quaternion.identity;
|
||||
|
||||
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)
|
||||
HoldNote holdSeg = segObj.GetComponent<HoldNote>();
|
||||
if (holdSeg != null)
|
||||
{
|
||||
JudgeManager.Instance.RegisterScheduledEndTime(holdNoteId.ToString(), scheduledEndTime);
|
||||
holdSeg.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), noteData.time, segmentDelay, isEndSegment, scheduledEndTime, noteData.color, key, partType);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("长音符片段缺少 HoldNote 组件!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.LogError("长音符片段缺少 HoldNote 组件!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private float CalculateSpeed()
|
||||
{
|
||||
float noteTravelTime = (60f / bpm) * 4;
|
||||
return 10.75f / noteTravelTime;
|
||||
}
|
||||
|
||||
public void ResetAllHoldNotes()
|
||||
private void Update()
|
||||
{
|
||||
JudgeManager.Instance.ResetAllHoldNotes();
|
||||
//Debug.Log("重置所有长音符状态");
|
||||
//if (globalGameTime.text=="0.01")
|
||||
//{
|
||||
// bgMusicAudioSource.Play();
|
||||
//}
|
||||
//else return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a8261645f54b8647ba4c89adc503be9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
fileFormatVersion: 2
|
||||
guid: 5a8261645f54b8647ba4c89adc503be9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -20,7 +20,7 @@ public class TrackKeyManager : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当音符进入判定区域时,添加按键映射
|
||||
/// 当音符进入判定区域时,添加按键映射
|
||||
/// </summary>
|
||||
public void RegisterKey(int trackIndex, KeyCode key)
|
||||
{
|
||||
@@ -31,7 +31,7 @@ public class TrackKeyManager : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当音符离开判定区域时,移除按键映射
|
||||
/// 当音符离开判定区域时,移除按键映射
|
||||
/// </summary>
|
||||
public void UnregisterKey(int trackIndex, KeyCode key)
|
||||
{
|
||||
@@ -45,7 +45,7 @@ public class TrackKeyManager : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取轨道当前的按键(队列头)
|
||||
/// 获取轨道当前的按键(队列头)
|
||||
/// </summary>
|
||||
public KeyCode GetCurrentKey(int trackIndex)
|
||||
{
|
||||
@@ -53,6 +53,6 @@ public class TrackKeyManager : MonoBehaviour
|
||||
{
|
||||
return trackKeyMappings[trackIndex].Peek();
|
||||
}
|
||||
return KeyCode.None; // 无按键
|
||||
return KeyCode.None; // 无按键
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user