Files
bansonic_beta_main/Assets/scripts/gamePlay_gameplay/NoteSpawner.cs
T

265 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
[Header("Global timing adjustments")]
[Tooltip("Global additional realtime offset (seconds) added to hit times for all notes. Use to test input latency or adjust judgement timing. Default 0. Can be negative to make notes arrive earlier.")]
public float globalHitDelay = 0f;
public NoteJudgeConfig judgeConfig; // 判定区间配置,需在 Inspector 赋值
private Beatmap beatmap;
private float startTime; // 存储歌曲开始时间
private bool isSpawning = false;
private static int holdNoteIdCounter = 0; // 全局唯一长音符 ID 计数器
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;
if (GameConfig.verboseLogs) 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;
}
if (GameConfig.verboseLogs) 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;
// pass realtime hit time to Note.Setup, include globalHitDelay
float rawHit = startTime + noteData.time + globalHitDelay;
float realtimeHit = Mathf.Max(0f, rawHit); // clamp to non-negative
noteScript.Setup(key, noteData.trackIndex, CalculateSpeed(), realtimeHit, noteData.color, judgeConfig, noteData);
if (GameConfig.verboseLogs) Debug.Log($"到达时间:{noteSpawnTime + (60f / bpm) * 4}");
}
else
{
Debug.LogError("音符预制体缺少 Note 组件!");
}
}
public void SpawnHoldNote(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} 对应的按键!");
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);
if (segmentCount < 1) segmentCount = 1; // 至少一个片段
// 如果用节拍计算的片段间隔与实际长度不整除,实际用于分段的间隔应当按实际长度均分,
// 以确保尾部音符排在最后一个中段之后并且到达时间等于谱面结束时间。
float actualSegmentInterval = noteData.length / segmentCount; // 实际用于中段间隔
Transform spawnPoint = spawnPoints[noteData.trackIndex];
// scheduledEndTime 使用谱面时间(note.time + length),但转换为实时
float rawScheduledEnd = startTime + (noteData.time + noteData.length) + globalHitDelay;
float scheduledEndTime = Mathf.Max(0f, rawScheduledEnd); // clamp to non-negative
// 生成唯一 id(改为自增计数器,避免随机冲突)
int holdNoteId = ++holdNoteIdCounter;
// 生成 Start 段
GameObject startObj = notePool.GetStartNote(noteData.color);
if (startObj == null)
{
Debug.LogError("对象池返回空 hold note start");
return;
}
startObj.transform.position = spawnPoint.position;
startObj.transform.rotation = Quaternion.identity;
HoldNote holdNote = startObj.GetComponent<HoldNote>();
if (holdNote != null)
{
// pass realtime hit time (startTime + note.time) and delay 0, include globalHitDelay
float rawStartHit = startTime + noteData.time + globalHitDelay;
float startHit = Mathf.Max(0f, rawStartHit);
holdNote.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), startHit, 0f, false, scheduledEndTime, noteData.color, key, "start", judgeConfig, noteData);
}
else
{
Debug.LogError("长音符 start 部分缺少 HoldNote 组件!");
return;
}
// 生成 Middle 片段(1 .. segmentCount-1
for (int i = 1; i < segmentCount; i++)
{
float segmentDelay = i * actualSegmentInterval; // 使用实际间隔
GameObject segObj = notePool.GetHoldNoteSegment(noteData.color);
if (segObj == null)
{
Debug.LogError("对象池返回空 hold note 片段!");
continue;
}
segObj.transform.position = spawnPoint.position;
segObj.transform.rotation = Quaternion.identity;
HoldNote holdSeg = segObj.GetComponent<HoldNote>();
if (holdSeg != null)
{
// 中段都标记为 middlepass realtime base time and segmentDelay
float rawBase = startTime + noteData.time + globalHitDelay;
float baseHit = Mathf.Max(0f, rawBase);
holdSeg.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), baseHit, segmentDelay, false, scheduledEndTime, noteData.color, key, "middle", judgeConfig, noteData);
}
else
{
Debug.LogError("长音符片段缺少 HoldNote 组件!");
}
}
// 始终在中段之后生成一个明确的 End 段(尾部音符)
GameObject endObj = notePool.GetHoldNoteEndSegment(noteData.color);
if (endObj == null)
{
Debug.LogError("对象池返回空 hold note 片段(end)!");
return;
}
endObj.transform.position = spawnPoint.position;
endObj.transform.rotation = Quaternion.identity;
// 为便于识别,将实例名追加后缀
if (GameConfig.verboseLogs)
endObj.name = endObj.name + "_end";
if (GameConfig.verboseLogs) Debug.Log($"生成尾部音符: {endObj.name} (holdId={holdNoteId}, color={noteData.color})");
HoldNote holdEnd = endObj.GetComponent<HoldNote>();
if (holdEnd != null)
{
// 将 delay 设置为整个 hold 长度 (或 segmentCount * actualSegmentInterval),确保尾部在最后中段之后
float endDelay = segmentCount * actualSegmentInterval; // 通常等于 noteData.length
float rawBase = startTime + noteData.time + globalHitDelay;
float baseHit = Mathf.Max(0f, rawBase);
holdEnd.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), baseHit, endDelay, true, scheduledEndTime, noteData.color, key, "end", judgeConfig, noteData);
}
else
{
Debug.LogError("长音符 end 部分缺少 HoldNote 组件!");
}
}
private float CalculateSpeed()
{
float noteTravelTime = (60f / bpm) * 4;
return 10.75f / noteTravelTime;
}
private void Update()
{
//if (globalGameTime.text=="0.01")
//{
// bgMusicAudioSource.Play();
//}
//else return;
}
}