original ver 25/06/01
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c76621295d0de4b4f9da7adbb0796b86
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
public class pressStart : MonoBehaviour
|
||||
{
|
||||
public string sceneName_toNextScene = "selectYourSongFirst";
|
||||
|
||||
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
//sceneName_toNextScene = "Songs_Select";
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Space))
|
||||
{
|
||||
SceneManager.LoadScene(sceneName_toNextScene);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2d7aa49ab1299745a8ba5b9cd7bdfe8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26b11bd92365316bdb654bc949357cf6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,111 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace EOUtils {
|
||||
/// <summary>
|
||||
/// 颜色(TODO:最好把这个移到别的文件中以避免过多对此文件的依赖)
|
||||
/// </summary>
|
||||
public enum Color
|
||||
{
|
||||
RED,
|
||||
// TODO
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public interface INote
|
||||
{
|
||||
/// <summary>
|
||||
/// Note 的状态
|
||||
/// </summary>
|
||||
public State state {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note 的颜色
|
||||
/// </summary>
|
||||
EOUtils.Color color {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note 开始 activate 的时间
|
||||
/// </summary>
|
||||
float startTime {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note 与判定线重叠的时间
|
||||
/// </summary>
|
||||
float hitTime {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note 的分数
|
||||
/// </summary>
|
||||
int noteScore {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note 的状态
|
||||
/// </summary>
|
||||
enum State {
|
||||
|
||||
/// <summary>
|
||||
/// Note 未被初始化。
|
||||
/// </summary>
|
||||
UNINITIALIZED,
|
||||
/// <summary>
|
||||
/// Note 已被预加载且未出现在场景中
|
||||
/// </summary>
|
||||
PRELOADED,
|
||||
/// <summary>
|
||||
/// Note 正在场景中且没有被聚焦
|
||||
/// </summary>
|
||||
ALIVE,
|
||||
/// <summary>
|
||||
/// Note 被聚焦
|
||||
/// </summary>
|
||||
FOCUSED,
|
||||
/// <summary>
|
||||
/// Note 已经消亡
|
||||
/// </summary>
|
||||
DIED
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 激活 Note
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">如果 Note 当前状态不能被激活</exception>
|
||||
void Activate();
|
||||
|
||||
/// <summary>
|
||||
/// 聚焦 Note。
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">如果 Note 当前状态不可以被聚焦</exception>
|
||||
void Focus();
|
||||
|
||||
/// <summary>
|
||||
/// 杀死 Note。
|
||||
/// </summary>
|
||||
void Kill();
|
||||
|
||||
/// <summary>
|
||||
/// 在 hit 输入时调用。如果
|
||||
/// </summary>
|
||||
/// <param name="time">输入 hit 时的游戏时间</param>
|
||||
/// <param name="accuracy">输出精准度</param>
|
||||
/// <returns>hit 是否对这个 hit 生效。如果 hit 没有生肖 accuracy 将会返回无效值</returns>
|
||||
bool hitInput(float time, out float accuracy);
|
||||
|
||||
/// <summary>
|
||||
/// 根据时间更新 Note 的位置。
|
||||
/// </summary>
|
||||
/// <param name="time">当前时间(单位:秒)</param>
|
||||
/// <param name="startPos">生成时的位置</param>
|
||||
/// <param name="hitPos">与判定线重合的位置</param>
|
||||
void UpdateMovement(float time, Vector2 startPos, Vector2 hitPos);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0d709646870071978c7ec811779595b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,4 @@
|
||||
public static class SongDataHolder
|
||||
{
|
||||
public static SongData SelectedSongData;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf953aa4e006ab04fbade2917f7a1149
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c72527919a8037245aaafb1a86cbb6b5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
public class back_toPreviousPage : MonoBehaviour
|
||||
{
|
||||
public string scene_turn_to_previous_scene = "selectYourSongFirst";
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void backto_PreviousPage()
|
||||
{
|
||||
SceneManager.LoadScene(scene_turn_to_previous_scene);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2718f2d44c51e8e44bbb08664c98b5b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4caae406373622a4180665e8a96a80d8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class AnimationController : MonoBehaviour
|
||||
{
|
||||
public GameObject redEffect; // 红色击打动画对象
|
||||
public GameObject greenEffect; // 绿色击打动画对象
|
||||
public GameObject yellowEffect; // 黄色击打动画对象
|
||||
public GameObject purpleEffect; // 紫色击打动画对象
|
||||
public GameObject blueEffect; // 蓝色击打动画对象
|
||||
|
||||
private Animator redAnimator;
|
||||
private Animator greenAnimator;
|
||||
private Animator yellowAnimator;
|
||||
private Animator purpleAnimator;
|
||||
private Animator blueAnimator;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 获取每个动画对象的 Animator 组件
|
||||
redAnimator = redEffect.GetComponent<Animator>();
|
||||
greenAnimator = greenEffect.GetComponent<Animator>();
|
||||
yellowAnimator = yellowEffect.GetComponent<Animator>();
|
||||
purpleAnimator = purpleEffect.GetComponent<Animator>();
|
||||
blueAnimator = blueEffect.GetComponent<Animator>();
|
||||
}
|
||||
|
||||
public void PlayDestroyAnimation(string color)
|
||||
{
|
||||
switch (color)
|
||||
{
|
||||
case "red":
|
||||
// 重置之前的触发器
|
||||
redAnimator.ResetTrigger("PlayRedDestroy");
|
||||
redAnimator.SetTrigger("PlayRedDestroy");
|
||||
break;
|
||||
case "green":
|
||||
greenAnimator.ResetTrigger("PlayGreenDestroy");
|
||||
greenAnimator.SetTrigger("PlayGreenDestroy");
|
||||
break;
|
||||
case "yellow":
|
||||
yellowAnimator.ResetTrigger("PlayYellowDestroy");
|
||||
yellowAnimator.SetTrigger("PlayYellowDestroy");
|
||||
break;
|
||||
case "purple":
|
||||
purpleAnimator.ResetTrigger("PlayPurpleDestroy");
|
||||
purpleAnimator.SetTrigger("PlayPurpleDestroy");
|
||||
break;
|
||||
case "blue":
|
||||
blueAnimator.ResetTrigger("PlayBlueDestroy");
|
||||
blueAnimator.SetTrigger("PlayBlueDestroy");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c9d972a8c1b8cd42836f774bb018416
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- redEffect: {instanceID: 0}
|
||||
- greenEffect: {instanceID: 0}
|
||||
- yellowEffect: {instanceID: 0}
|
||||
- purpleEffect: {instanceID: 0}
|
||||
- blueEffect: {instanceID: 0}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
public abstract class BaseNote : MonoBehaviour
|
||||
{
|
||||
public int TrackIndex { get; protected set; }
|
||||
public string NoteColor { get; protected set; }
|
||||
public float Speed { get; protected set; }
|
||||
protected float hitTime;
|
||||
|
||||
public virtual void Initialize(int trackIndex, string noteColor, float speed, float hitTime)
|
||||
{
|
||||
TrackIndex = trackIndex;
|
||||
NoteColor = noteColor;
|
||||
Speed = speed;
|
||||
this.hitTime = hitTime;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1685ef287fcd6ee449c4bc269594b792
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
[Serializable]
|
||||
public class NoteData
|
||||
{
|
||||
public int trackIndex; // 轨道索引
|
||||
public float time; // 音符出现时间(秒)
|
||||
public string color; // 音符颜色(如 "red", "blue")
|
||||
public string type; // 音符类型("tap" = 单击,"hold" = 长按)
|
||||
public float length; // 音符长度(仅用于长按音符,单位:秒)
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TrackData
|
||||
{
|
||||
public string color; // 轨道颜色
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Beatmap
|
||||
{
|
||||
public string title; // 歌曲名
|
||||
public string composer; // 作曲家
|
||||
public string illustrator; // 画师
|
||||
public string charter; // 谱师
|
||||
public string beatmapId; // 谱面自定编号
|
||||
public float duration; // 歌曲时长(秒)
|
||||
public float bpm; // BPM
|
||||
public int difficulty; // 难度(数字)
|
||||
public string difficultyName;// 难度代号(如 "Easy", "Hard", "Expert")
|
||||
public string createdDate; // 谱面创建日期(字符串格式:yyyy-MM-dd)
|
||||
public string musicFile; // 音乐文件路径
|
||||
public string backgroundFile;// 曲绘文件路径
|
||||
|
||||
public TrackData[] tracks; // 轨道信息(每个轨道的颜色)
|
||||
public NoteData[] notes; // 音符列表
|
||||
|
||||
public bool is_official = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4938dcd8ed83be145a4ee5e19e480fab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,131 @@
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
|
||||
public class BeatmapManager : MonoBehaviour
|
||||
|
||||
{
|
||||
public Beatmap beatmap; // 当前谱面数据
|
||||
|
||||
// 音符生成器引用
|
||||
public NoteSpawner noteSpawner;
|
||||
|
||||
|
||||
|
||||
// 保存谱面数据到 JSON 文件
|
||||
//public void SaveBeatmap(string fileName)
|
||||
//{
|
||||
// string json = JsonUtility.ToJson(beatmap, true); // 格式化 JSON 输出
|
||||
// File.WriteAllText(Application.dataPath + "/" + fileName, json);
|
||||
// Debug.Log("谱面已保存:" + Application.dataPath + "/" + fileName);
|
||||
//}
|
||||
|
||||
// 从 JSON 文件加载谱面数据
|
||||
public void LoadBeatmap(string fileName)
|
||||
{
|
||||
string path = Application.streamingAssetsPath + "/" + fileName;
|
||||
if (File.Exists(path))
|
||||
{
|
||||
string json = File.ReadAllText(path);
|
||||
beatmap = JsonUtility.FromJson<Beatmap>(json);
|
||||
Debug.Log("谱面已加载:" + json);
|
||||
|
||||
// 在加载后立即调用音符生成
|
||||
noteSpawner.LoadBeatmap(beatmap);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("文件不存在:" + path);
|
||||
}
|
||||
}
|
||||
public void LoadBeatmap(Beatmap loadedBeatmap)
|
||||
{
|
||||
beatmap = loadedBeatmap;
|
||||
Debug.Log("谱面已加载:" + beatmap.title);
|
||||
|
||||
// 在加载后立即调用音符生成
|
||||
noteSpawner.LoadBeatmap(beatmap);
|
||||
}
|
||||
|
||||
//public void LoadBeatmapFromFile(string fileName)
|
||||
//{
|
||||
// string path = Application.persistentDataPath + "/" + fileName;
|
||||
// if (File.Exists(path))
|
||||
// {
|
||||
// string json = File.ReadAllText(path);
|
||||
// Beatmap beatmap = JsonUtility.FromJson<Beatmap>(json);
|
||||
// beatmapManager.LoadBeatmap(beatmap); // 使用 LoadBeatmap 方法传递 Beatmap 对象
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Debug.LogError("文件不存在:" + path);
|
||||
// }
|
||||
//}
|
||||
|
||||
// 创建一个示例谱面并保存
|
||||
//public void CreateSampleBeatmap()
|
||||
//{
|
||||
// beatmap = new Beatmap
|
||||
// {
|
||||
// title = "Moonlight Sonata",
|
||||
// composer = "Ludwig van Beethoven",
|
||||
// illustrator = "John Doe",
|
||||
// charter = "Jane Smith",
|
||||
// beatmapId = "ML-001",
|
||||
// duration = 120.5f,
|
||||
// bpm = 128f,
|
||||
// difficulty = 5,
|
||||
// difficultyName = "Hard",
|
||||
// createdDate = System.DateTime.Now.ToString("yyyy-MM-dd"),
|
||||
// musicFile = "moonlight_sonata.mp3",
|
||||
// backgroundFile = "moonlight_art.png",
|
||||
|
||||
// tracks = new TrackData[]
|
||||
// {
|
||||
// new TrackData { color = "red" },
|
||||
// new TrackData { color = "green" },
|
||||
// new TrackData { color = "yellow" },
|
||||
// new TrackData { color = "purple" },
|
||||
// new TrackData { color = "blue" }
|
||||
// },
|
||||
|
||||
// notes = new NoteData[]
|
||||
// {
|
||||
// new NoteData { trackIndex = 0, time = 1.0f, color = "red", type = "tap", length = 0.0f },
|
||||
// new NoteData { trackIndex = 4, time = 2.0f, color = "blue", type = "tap", length = 0.0f },
|
||||
// new NoteData { trackIndex = 1, time = 3.5f, color = "green", type = "tap", length = 0.0f },
|
||||
// new NoteData { trackIndex = 2, time = 3.5f, color = "yellow", type = "tap", length = 0.0f },
|
||||
// new NoteData { trackIndex = 2, time = 3.5f, color = "yellow", type = "tap", length = 0.0f },
|
||||
// new NoteData { trackIndex = 3, time = 3.5f, color = "purple", type = "tap", length = 0.0f },
|
||||
// }
|
||||
// };
|
||||
|
||||
// SaveBeatmap("test_beatmap.json");
|
||||
//}
|
||||
|
||||
// 读取并加载当前谱面到音符生成器
|
||||
public void LoadBeatmapFromFile()
|
||||
{
|
||||
string path = Application.streamingAssetsPath + "/Emilia_demo.json";
|
||||
if (File.Exists(path))
|
||||
{
|
||||
string json = File.ReadAllText(path);
|
||||
beatmap = JsonUtility.FromJson<Beatmap>(json);
|
||||
noteSpawner.LoadBeatmap(beatmap);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("谱面文件未找到!");
|
||||
}
|
||||
}
|
||||
|
||||
// 在 Start() 方法中初始化
|
||||
void Start()
|
||||
{
|
||||
// 这里可以选择在启动时创建一个示例谱面并加载
|
||||
//CreateSampleBeatmap(); // 创建并保存一个示例谱面
|
||||
LoadBeatmap("Emilia_demo.json"); // 加载并实例化音符
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c80308880c1feca40bcf476848142e2b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
public class GameManager : MonoBehaviour
|
||||
{
|
||||
public BeatmapManager beatmapManager; // 负责加载和管理谱面
|
||||
public NoteSpawner noteSpawner; // 音符生成器
|
||||
public AudioSource musicSource; // 音乐播放器
|
||||
|
||||
void Start()
|
||||
{
|
||||
Debug.Log("GameManager Start() 被调用");
|
||||
|
||||
// 检查关键组件是否为空
|
||||
if (beatmapManager == null) Debug.LogError("beatmapManager 未赋值!");
|
||||
if (noteSpawner == null) Debug.LogError("noteSpawner 未赋值!");
|
||||
if (musicSource == null) Debug.LogError("musicSource 未赋值!");
|
||||
|
||||
// 在游戏开始时加载并初始化谱面
|
||||
string beatmapFilePath = Path.Combine(Application.streamingAssetsPath, "Emilia_demo.json");
|
||||
|
||||
if (!File.Exists(beatmapFilePath))
|
||||
{
|
||||
Debug.LogError($"谱面文件不存在: {beatmapFilePath}");
|
||||
return;
|
||||
}
|
||||
|
||||
string json = File.ReadAllText(beatmapFilePath);
|
||||
Beatmap beatmap = JsonUtility.FromJson<Beatmap>(json); // 解析 JSON 为 Beatmap 对象
|
||||
|
||||
if (beatmap == null)
|
||||
{
|
||||
Debug.LogError("谱面解析失败,JSON 格式可能错误!");
|
||||
return;
|
||||
}
|
||||
|
||||
beatmapManager.LoadBeatmap(beatmap); // 传递给 BeatmapManager
|
||||
noteSpawner.LoadBeatmap(beatmap); // 传递给 NoteSpawner
|
||||
|
||||
// 播放背景音乐
|
||||
if (!string.IsNullOrEmpty(beatmap.musicFile))
|
||||
{
|
||||
AudioClip musicClip = Resources.Load<AudioClip>(beatmap.musicFile);
|
||||
if (musicClip == null)
|
||||
{
|
||||
Debug.LogError($"音乐文件加载失败: {beatmap.musicFile}");
|
||||
}
|
||||
else
|
||||
{
|
||||
musicSource.clip = musicClip;
|
||||
//musicSource.Play();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("谱面中 musicFile 为空!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a3cb3ef9478edd4a8b6718a11a0fc3c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class GlobalInputManager : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55c02b3cb1ce2094990e79779b31a131
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,320 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
public enum NoteSegment { None, Start, Middle, End }
|
||||
|
||||
[RequireComponent(typeof(HoldNoteController))]
|
||||
public class HoldNote : BaseNote
|
||||
{
|
||||
private KeyCode keyToPress = KeyCode.None;
|
||||
private string noteColor = string.Empty;
|
||||
private string noteID = string.Empty;
|
||||
private NoteSegment segment = NoteSegment.None;
|
||||
|
||||
private float hitTime = 0f;
|
||||
private float releaseTime = 0f;
|
||||
private bool hasReleased = true;
|
||||
private bool hasEnteredLine = false;
|
||||
|
||||
private Coroutine autoReturnCoroutine;
|
||||
private HoldNoteController controller;
|
||||
|
||||
public int id;
|
||||
public int trackIndex;
|
||||
public float speed;
|
||||
public float startTime;
|
||||
public float delay;
|
||||
public bool isEnd;
|
||||
public float scheduledEndTime;
|
||||
public string color;
|
||||
public KeyCode key;
|
||||
public string type; // "start", "middle", or "end"
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
controller = GetComponent<HoldNoteController>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
ResetState();
|
||||
}
|
||||
|
||||
public void Setup(int id, int trackIndex, float speed, float time, float delay,
|
||||
bool isEnd, float scheduledEnd, string color, KeyCode key, string type)
|
||||
{
|
||||
this.id = id;
|
||||
this.trackIndex = trackIndex;
|
||||
this.speed = speed;
|
||||
this.startTime = time;
|
||||
this.delay = delay;
|
||||
this.isEnd = isEnd;
|
||||
this.scheduledEndTime = scheduledEnd;
|
||||
this.color = color;
|
||||
this.key = key;
|
||||
this.type = type;
|
||||
|
||||
this.noteID = id.ToString();
|
||||
this.keyToPress = key;
|
||||
this.noteColor = color;
|
||||
this.hitTime = time + delay;
|
||||
this.segment = (delay == 0f) ? NoteSegment.Start :
|
||||
(isEnd ? NoteSegment.End : NoteSegment.Middle);
|
||||
this.hasEnteredLine = false;
|
||||
this.hasReleased = false;
|
||||
|
||||
JudgeManager.Instance?.RegisterNoteReleased(noteID, false);
|
||||
if (segment == NoteSegment.Start)
|
||||
JudgeManager.Instance?.RegisterStartJudged(noteID, false);
|
||||
|
||||
if (segment == NoteSegment.End)
|
||||
JudgeManager.Instance?.RegisterScheduledEndTime(noteID, scheduledEndTime);
|
||||
|
||||
controller?.SetSpeed(speed);
|
||||
controller?.SetSegmentDelay(delay);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (segment == NoteSegment.Start && Input.GetKeyDown(keyToPress))
|
||||
{
|
||||
if (hasEnteredLine)
|
||||
HandleStart();
|
||||
}
|
||||
else if (segment == NoteSegment.End && Input.GetKeyUp(keyToPress))
|
||||
{
|
||||
HandleEnd();
|
||||
}
|
||||
|
||||
if (segment == NoteSegment.Middle && hasEnteredLine)
|
||||
{
|
||||
if (JudgeManager.Instance.IsStartJudged(noteID) &&
|
||||
!JudgeManager.Instance.HasNoteReleased(noteID) &&
|
||||
Input.GetKey(keyToPress))
|
||||
{
|
||||
// 不再立即回收,由协程等待 hitTime 再回收
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D collision)
|
||||
{
|
||||
if (!collision.CompareTag("JudgmentLine")) return;
|
||||
|
||||
if (segment == NoteSegment.Start || segment == NoteSegment.Middle)
|
||||
{
|
||||
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)}");
|
||||
|
||||
if (autoReturnCoroutine != null)
|
||||
StopCoroutine(autoReturnCoroutine);
|
||||
|
||||
autoReturnCoroutine = StartCoroutine(DelayedAutoReturnCheck());
|
||||
}
|
||||
|
||||
if (segment == NoteSegment.End)
|
||||
{
|
||||
if (autoReturnCoroutine != null)
|
||||
StopCoroutine(autoReturnCoroutine);
|
||||
|
||||
autoReturnCoroutine = StartCoroutine(DelayedAutoReturnCheck());
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit2D(Collider2D collision)
|
||||
{
|
||||
if (!collision.CompareTag("JudgmentLine")) return;
|
||||
|
||||
if (segment == NoteSegment.Start)
|
||||
{
|
||||
if (!JudgeManager.Instance.IsStartJudged(noteID))
|
||||
{
|
||||
ReturnToPool();
|
||||
}
|
||||
}
|
||||
if (segment == NoteSegment.Middle)
|
||||
{
|
||||
if (!JudgeManager.Instance.IsStartJudged(noteID) ||
|
||||
JudgeManager.Instance.HasNoteReleased(noteID))
|
||||
{
|
||||
Debug.Log($"[HoldNote] Middle段离开判定线强制回收: {noteColor}");
|
||||
ReturnToPool();
|
||||
}
|
||||
}
|
||||
else if (segment == NoteSegment.End)
|
||||
{
|
||||
if (!JudgeManager.Instance.IsStartJudged(noteID) || JudgeManager.Instance.HasNoteReleased(noteID))
|
||||
{
|
||||
ReturnToPool();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator DelayedAutoReturnCheck()
|
||||
{
|
||||
yield return null;
|
||||
|
||||
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)
|
||||
{
|
||||
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))
|
||||
{
|
||||
Debug.Log($"[HoldNote] Middle段在 hitTime 达成条件,回收: {noteColor}");
|
||||
ReturnToPool();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"[HoldNote] Middle段在 hitTime 条件不符,不回收: {noteColor}");
|
||||
}
|
||||
}
|
||||
else if (segment == NoteSegment.End && !hasReleased)
|
||||
{
|
||||
Debug.Log($"[HoldNote] End段延迟检查后仍未释放: {noteColor}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"[HoldNote] DelayedAutoReturnCheck 无需处理: segment={segment}, color={noteColor}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleStart()
|
||||
{
|
||||
float pressTime = Time.time;
|
||||
float offset = Mathf.Abs(pressTime - hitTime);
|
||||
|
||||
if (offset < 0.1f)
|
||||
{
|
||||
Debug.Log($"[HoldNote] START判定成功(偏差={offset:F2}秒): {noteColor}");
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"[HoldNote] START Miss(偏差={offset:F2}秒): {noteColor}");
|
||||
}
|
||||
|
||||
StartCoroutine(DelayedReturn());
|
||||
}
|
||||
|
||||
private IEnumerator DelayedReturn()
|
||||
{
|
||||
yield return new WaitForSeconds(0.05f);
|
||||
ReturnToPool();
|
||||
}
|
||||
|
||||
private void HandleEnd()
|
||||
{
|
||||
releaseTime = Time.time;
|
||||
hasReleased = true;
|
||||
|
||||
JudgeManager.Instance.RegisterNoteReleased(noteID, true);
|
||||
|
||||
float diff = Mathf.Abs(releaseTime - scheduledEndTime);
|
||||
string result;
|
||||
|
||||
if (releaseTime >= scheduledEndTime)
|
||||
{
|
||||
result = "Perfect";
|
||||
}
|
||||
else if (diff < 0.1f)
|
||||
{
|
||||
result = "Perfect";
|
||||
}
|
||||
else if (diff < 0.5f)
|
||||
{
|
||||
result = "Great";
|
||||
}
|
||||
else
|
||||
{
|
||||
result = "Good";
|
||||
}
|
||||
|
||||
Debug.Log($"[HoldNote] END判定结果: {noteColor} {result} (release={releaseTime:F2}, target={scheduledEndTime:F2})");
|
||||
|
||||
ReturnToPool();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (autoReturnCoroutine != null)
|
||||
{
|
||||
StopCoroutine(autoReturnCoroutine);
|
||||
autoReturnCoroutine = null;
|
||||
}
|
||||
|
||||
if (segment == NoteSegment.End &&
|
||||
!hasReleased &&
|
||||
!JudgeManager.Instance.HasNoteReleased(noteID) &&
|
||||
keyToPress != KeyCode.None &&
|
||||
!Input.GetKey(keyToPress))
|
||||
{
|
||||
hasReleased = true;
|
||||
JudgeManager.Instance.RegisterNoteReleased(noteID, true);
|
||||
Debug.Log($"[HoldNote] OnDisable补偿KeyUp: {noteColor}");
|
||||
}
|
||||
|
||||
controller?.StopMovement();
|
||||
}
|
||||
|
||||
private void ReturnToPool()
|
||||
{
|
||||
if (!gameObject.activeSelf) return;
|
||||
|
||||
if (segment == NoteSegment.Start && !hasEnteredLine)
|
||||
{
|
||||
Debug.LogWarning($"[HoldNote] 阻止回收尚未进入判定线的 Start段: {noteColor}");
|
||||
return;
|
||||
}
|
||||
|
||||
gameObject.SetActive(false);
|
||||
|
||||
if (segment == NoteSegment.Start)
|
||||
NotePool.Instance.ReturnStartNote(gameObject, noteColor);
|
||||
else
|
||||
NotePool.Instance.ReturnHoldNoteSegment(gameObject, noteColor);
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
hasReleased = false;
|
||||
segment = NoteSegment.None;
|
||||
keyToPress = KeyCode.None;
|
||||
noteColor = string.Empty;
|
||||
noteID = string.Empty;
|
||||
hitTime = 0f;
|
||||
scheduledEndTime = 0f;
|
||||
hasEnteredLine = false;
|
||||
|
||||
if (autoReturnCoroutine != null)
|
||||
{
|
||||
StopCoroutine(autoReturnCoroutine);
|
||||
autoReturnCoroutine = null;
|
||||
}
|
||||
|
||||
controller?.StopMovement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86977693d5a2d364bb086303f60ca1b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
public class HoldNoteController : MonoBehaviour
|
||||
{
|
||||
private bool isInsideJudgeZone = false;
|
||||
public event Action<bool> OnJudgeZoneChanged;
|
||||
private bool isMoving = false;
|
||||
private float speed = 0f; // 音符下落速度
|
||||
private float activationTime; // 用于延迟启用下落
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// **控制音符下落**
|
||||
if (!isMoving && Time.time >= activationTime)
|
||||
{
|
||||
isMoving = true;
|
||||
}
|
||||
|
||||
if (isMoving)
|
||||
{
|
||||
transform.Translate(Vector3.down * speed * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置音符延迟下落的时间
|
||||
/// </summary>
|
||||
public void SetSegmentDelay(float segmentDelay)
|
||||
{
|
||||
activationTime = Time.time + segmentDelay;
|
||||
isMoving = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置音符的下落速度
|
||||
/// </summary>
|
||||
public void SetSpeed(float newSpeed)
|
||||
{
|
||||
speed = newSpeed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止音符下落(当音符被击中或Miss时)
|
||||
/// </summary>
|
||||
public void StopMovement()
|
||||
{
|
||||
isMoving = false;
|
||||
}
|
||||
private void OnTriggerEnter2D(Collider2D collision)
|
||||
{
|
||||
if (!collision.CompareTag("JudgmentLine")) return;
|
||||
|
||||
isInsideJudgeZone = true;
|
||||
OnJudgeZoneChanged?.Invoke(true);
|
||||
}
|
||||
|
||||
private void OnTriggerExit2D(Collider2D collision)
|
||||
{
|
||||
if (!collision.CompareTag("JudgmentLine")) return;
|
||||
|
||||
isInsideJudgeZone = false;
|
||||
OnJudgeZoneChanged?.Invoke(false);
|
||||
}
|
||||
|
||||
public bool IsInsideJudgArea()
|
||||
{
|
||||
return isInsideJudgeZone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d40c6ec60e52049429740ffb978efab5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
public class InputManager : MonoBehaviour
|
||||
{
|
||||
public static event Action<KeyCode> OnKeyPressed;
|
||||
public static event Action<KeyCode> OnKeyReleased;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
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))
|
||||
{
|
||||
OnKeyPressed?.Invoke(key);
|
||||
// 调用 JudgeManager 统一判断最早的音符
|
||||
JudgeManager.Instance.JudgeEarliestNote(key);
|
||||
}
|
||||
if (Input.GetKeyUp(key))
|
||||
OnKeyReleased?.Invoke(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,122 @@
|
||||
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>();
|
||||
// 记录 End 段的 scheduledEndTime(防止 End 被回收后仍然能判定)
|
||||
private Dictionary<string, float> noteEndTimes = new Dictionary<string, float>();
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录某个颜色的长音符 End 段的 scheduledEndTime
|
||||
/// </summary>
|
||||
public void RegisterScheduledEndTime(string noteColor, float endTime)
|
||||
{
|
||||
noteEndTimes[noteColor] = endTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取某个颜色的 End 段的 scheduledEndTime
|
||||
/// </summary>
|
||||
public float GetScheduledEndTime(string noteColor)
|
||||
{
|
||||
return noteEndTimes.ContainsKey(noteColor) ? noteEndTimes[noteColor] : 0f;
|
||||
}
|
||||
|
||||
public void RegisterStartJudged(string noteID, bool state)
|
||||
{
|
||||
startJudgedNotes[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];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当音符进入判定区域时调用
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当音符离开判定区域时调用
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
// **如果音符未被判定,则触发 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(); // 只取最早的音符
|
||||
if (note != null && !note.IsJudged())
|
||||
{
|
||||
note.Judge();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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,130 @@
|
||||
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;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
InputManager.OnKeyPressed -= HandlePress;
|
||||
}
|
||||
|
||||
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,12 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
public enum NoteColor
|
||||
{
|
||||
Red, // ºìÉ«Òô·û
|
||||
Green, // ÂÌÉ«Òô·û
|
||||
Yellow, // »ÆÉ«Òô·û
|
||||
Purple, // ×ÏÉ«Òô·û
|
||||
Blue // À¶É«Òô·û
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca21a5e0d2736724bb9a96eeca2b19dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,101 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
public class NoteController : MonoBehaviour
|
||||
{
|
||||
private float speed;
|
||||
private bool isMoving = true;
|
||||
private bool isInJudgeZone = false; // 判定区域标记
|
||||
|
||||
private ParticleSystem hitEffect;
|
||||
private Note linkedNote;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
|
||||
hitEffect = GetComponentInChildren<ParticleSystem>();
|
||||
linkedNote = GetComponent<Note>();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (isMoving)
|
||||
{
|
||||
transform.Translate(Vector3.down * speed * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSpeed(float newSpeed)
|
||||
{
|
||||
speed = newSpeed;
|
||||
}
|
||||
|
||||
public void StopMovement()
|
||||
{
|
||||
isMoving = false;
|
||||
}
|
||||
|
||||
public void PlayHitEffect()
|
||||
{
|
||||
if (hitEffect != null)
|
||||
{
|
||||
hitEffect.Play();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
speed = 0f;
|
||||
isMoving = true;
|
||||
isInJudgeZone = false;
|
||||
|
||||
if (hitEffect != null)
|
||||
{
|
||||
hitEffect.Stop();
|
||||
hitEffect.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 将 OnTriggerEnter2D 和 OnTriggerExit2D 挂载在 Controller 上
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D collision)
|
||||
{
|
||||
if (collision.CompareTag("JudgmentLine"))
|
||||
{
|
||||
if (linkedNote != null)
|
||||
{
|
||||
isInJudgeZone = true;
|
||||
TrackKeyManager.Instance.RegisterKey(linkedNote.GetTrackIndex(), linkedNote.GetKey());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit2D(Collider2D collision)
|
||||
{
|
||||
if (collision.CompareTag("JudgmentLine"))
|
||||
{
|
||||
if (linkedNote != null)
|
||||
{
|
||||
isInJudgeZone = false;
|
||||
TrackKeyManager.Instance.UnregisterKey(linkedNote.GetTrackIndex(), linkedNote.GetKey());
|
||||
linkedNote.SetJudgeZone(isInJudgeZone);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 供 Note 查询是否处于判定区域
|
||||
/// </summary>
|
||||
public bool IsInJudgeZone()
|
||||
{
|
||||
return isInJudgeZone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c62655f9ac1675e46ad7ea9a302508ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,190 @@
|
||||
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)
|
||||
{
|
||||
|
||||
NoteController noteScript = note.GetComponent<NoteController>();
|
||||
if (noteScript != null)
|
||||
{
|
||||
noteScript.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,217 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using Unity.VisualScripting;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
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 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;
|
||||
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.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 = Random.Range(100000, 999999);
|
||||
|
||||
// 生成 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)
|
||||
{
|
||||
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++)
|
||||
{
|
||||
float segmentDelay = i * segmentInterval;
|
||||
bool isEndSegment = (i == segmentCount - 1);
|
||||
string partType = isEndSegment ? "end" : "middle";
|
||||
|
||||
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)
|
||||
{
|
||||
holdSeg.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), noteData.time, segmentDelay, isEndSegment, scheduledEndTime, noteData.color, key, partType);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("长音符片段缺少 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a8261645f54b8647ba4c89adc503be9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class TrackKeyManager : MonoBehaviour
|
||||
{
|
||||
public static TrackKeyManager Instance { get; private set; }
|
||||
|
||||
private Dictionary<int, Queue<KeyCode>> trackKeyMappings = new Dictionary<int, Queue<KeyCode>>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当音符进入判定区域时,添加按键映射
|
||||
/// </summary>
|
||||
public void RegisterKey(int trackIndex, KeyCode key)
|
||||
{
|
||||
if (!trackKeyMappings.ContainsKey(trackIndex))
|
||||
trackKeyMappings[trackIndex] = new Queue<KeyCode>();
|
||||
|
||||
trackKeyMappings[trackIndex].Enqueue(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当音符离开判定区域时,移除按键映射
|
||||
/// </summary>
|
||||
public void UnregisterKey(int trackIndex, KeyCode key)
|
||||
{
|
||||
if (!trackKeyMappings.ContainsKey(trackIndex) || trackKeyMappings[trackIndex].Count == 0)
|
||||
return;
|
||||
|
||||
if (trackKeyMappings[trackIndex].Peek() == key)
|
||||
{
|
||||
trackKeyMappings[trackIndex].Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取轨道当前的按键(队列头)
|
||||
/// </summary>
|
||||
public KeyCode GetCurrentKey(int trackIndex)
|
||||
{
|
||||
if (trackKeyMappings.ContainsKey(trackIndex) && trackKeyMappings[trackIndex].Count > 0)
|
||||
{
|
||||
return trackKeyMappings[trackIndex].Peek();
|
||||
}
|
||||
return KeyCode.None; // 无按键
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab4cc3cc2f90c3a4b9bc6a5dea674684
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class gameplay_global : MonoBehaviour
|
||||
{
|
||||
public static gameplay_global Instance; //单例
|
||||
|
||||
public bool game_isStarted = false; //游戏开始
|
||||
public bool game_isPaused = false; //游戏暂停
|
||||
public bool game_isEnded = false; //游戏结束,读到了最后的chart_is_end
|
||||
|
||||
public bool k1_pressed = false; //按下了按键
|
||||
public bool k2_pressed = false;
|
||||
public bool k3_pressed = false;
|
||||
public bool k4_pressed = false;
|
||||
public bool k5_pressed = false;
|
||||
|
||||
public bool k1_inJudgeZone = false;//进入了判定区域
|
||||
public bool k2_inJudgeZone = false;
|
||||
public bool k3_inJudgeZone = false;
|
||||
public bool k4_inJudgeZone = false;
|
||||
public bool k5_inJudgeZone = false;
|
||||
|
||||
|
||||
public string[] notes;
|
||||
public string songName;
|
||||
public int difficulty_ID;
|
||||
public string difficulty_Name;
|
||||
|
||||
public float startTime; //记录开始游戏的时间 用于谱面note加载时机计算
|
||||
public float streamMultiply = 5f; //note流速
|
||||
public float offset = -0.2f; //延迟
|
||||
public float lifeTime = 0.15f; //perfect good lost判定提示效果的显示时间
|
||||
public float top_position_Y; //top按钮的y坐标 用于确定note生成位置的y坐标
|
||||
public int perfectScore;//perfect分值
|
||||
public int goodScore;//good分值
|
||||
public int score;//float类型的精确总分
|
||||
public float multiNoteOffset = 0.26f;//用于多押调整错位的偏移量
|
||||
public int combo = 0;//连击数
|
||||
public int noteSum = 0;//谱面总物量
|
||||
public int perfectNum = 0;//当前perfect数
|
||||
public int goodNum = 0;//当前good数
|
||||
public int missNum = 0;//当前miss数
|
||||
public Vector3 k1Pos, k2Pos, k3Pos, k4Pos; //四个键位的坐标 用于确定note生成位置
|
||||
public KeyCode key1 = KeyCode.D;
|
||||
public KeyCode key2 = KeyCode.F;
|
||||
public KeyCode key3 = KeyCode.J;
|
||||
public KeyCode key4 = KeyCode.K;
|
||||
|
||||
// Start is called before the first frame update
|
||||
private void Awake()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe9f6b9f7cd4f444cb71251541a9cb29
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb4668e030e687a419b45cfe6c9dcb00
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
|
||||
public class runningTime : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
|
||||
public TextMeshProUGUI allRunningTime;
|
||||
public AudioSource bgMusicAudioSource;
|
||||
|
||||
private bool isPlayingSong = false;
|
||||
|
||||
void Start()
|
||||
{
|
||||
//allRunningTime.text = "0.00";
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
allRunningTime.text = Time.time.ToString("F2");
|
||||
if (Time.time > 0 && isPlayingSong == false)
|
||||
{
|
||||
bgMusicAudioSource.Play(4);
|
||||
isPlayingSong = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab0a616a44311d544a9ec3c067704b56
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9102e3024c1ddc4989d5b7b97f53e50
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
public class returnMainMenu : MonoBehaviour
|
||||
{
|
||||
private string sceneName_songsSelectFirst_toNextScene = "Main_main";
|
||||
|
||||
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void ReturnToMainMenu_fromSongsSelectFirst()
|
||||
{
|
||||
SceneManager.LoadScene(sceneName_songsSelectFirst_toNextScene);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34651ae670880384a9751310adc16e38
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user