Files
Cielonos/Assets/Scripts/MainGame/Managers/CombatManager/CombatSystems/MusicBeatSystem/MusicBeatData.cs
SoulliesOfficial b5cb6152ff MusicBeat
2026-05-26 00:21:27 -04:00

244 lines
7.6 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 System.Collections.Generic;
using AK.Wwise;
using Sirenix.OdinInspector;
using UnityEngine;
using Event = AK.Wwise.Event;
namespace Cielonos.MainGame
{
/// <summary>
/// 音乐谱面数据容器,存储 BGM 元信息和手动/自动生成的节拍标记
/// </summary>
[CreateAssetMenu(fileName = "MusicBeatData", menuName = "Cielonos/CombatSystem/MusicBeat/Data", order = 0)]
public class MusicBeatData : SerializedScriptableObject
{
private const float DEFAULT_BPM = 120f;
private const int DEFAULT_BEATS_PER_BAR = 4;
[TitleGroup("Wwise Configuration")]
[Tooltip("Wwise 音乐 Switch用于切换 BGM 变体")]
public Switch musicSwitch;
[TitleGroup("Wwise Configuration")]
[Tooltip("播放 BGM 使用的 Wwise Event需注册 MusicSync 回调)")]
public Event musicEvent;
[TitleGroup("Wwise Configuration")]
[Tooltip("停止 BGM 使用的 Wwise Event")]
public Event stopMusicEvent;
[TitleGroup("Music Properties")]
[Tooltip("基准 BPM")]
[MinValue(1f)]
public float bpm = DEFAULT_BPM;
[TitleGroup("Music Properties")]
[Tooltip("每小节拍数")]
[MinValue(1)]
public int beatsPerBar = DEFAULT_BEATS_PER_BAR;
[TitleGroup("Music Properties")]
[Tooltip("音频起始偏移量(秒),用于对齐第一拍与音频起始的差异")]
public float audioStartOffset;
[TitleGroup("Music Properties")]
[Tooltip("音乐总时长(秒)")]
[MinValue(0f)]
public float totalDuration;
[TitleGroup("Beat Markers")]
[Tooltip("手动标记的节拍列表")]
[ListDrawerSettings(ShowFoldout = true)]
public List<BeatMarker> beatMarkers = new List<BeatMarker>();
/// <summary>
/// 单拍间隔时间(秒)
/// </summary>
public float BeatInterval => 60f / bpm;
/// <summary>
/// 单小节时长(秒)
/// </summary>
public float BarDuration => BeatInterval * beatsPerBar;
/// <summary>
/// 获取指定时间之前的最后一个节拍标记
/// </summary>
/// <param name="time"></param>
/// <returns></returns>
public BeatMarker GetLastBeat(float time)
{
if (beatMarkers == null || beatMarkers.Count == 0) return null;
BeatMarker last = null;
for (int i = 0; i < beatMarkers.Count; i++)
{
if (beatMarkers[i].time <= time)
{
last = beatMarkers[i];
}
else
{
break;
}
}
return last;
}
/// <summary>
/// 获取最接近指定时间的节拍标记
/// </summary>
/// <param name="time">目标时间(秒)</param>
/// <param name="tolerance">最大容差(秒),超出此范围返回 null</param>
/// <returns>最近的节拍标记,若超出容差则返回 null</returns>
public BeatMarker GetNearestBeat(float time, float tolerance)
{
if (beatMarkers == null || beatMarkers.Count == 0) return null;
BeatMarker nearest = null;
float minDiff = float.MaxValue;
for (int i = 0; i < beatMarkers.Count; i++)
{
float diff = Mathf.Abs(beatMarkers[i].time - time);
if (diff < minDiff)
{
minDiff = diff;
nearest = beatMarkers[i];
}
}
return minDiff <= tolerance ? nearest : null;
}
/// <summary>
/// 获取指定时间之后最近的节拍标记
/// </summary>
public BeatMarker GetNextBeat(float currentTime)
{
if (beatMarkers == null) return null;
for (int i = 0; i < beatMarkers.Count; i++)
{
if (beatMarkers[i].time > currentTime)
{
return beatMarkers[i];
}
}
return null;
}
/// <summary>
/// 获取指定时间之后最近的、带有特定 tag 的节拍标记
/// </summary>
public BeatMarker GetNextBeatWithTag(float currentTime, string tag)
{
if (beatMarkers == null) return null;
for (int i = 0; i < beatMarkers.Count; i++)
{
if (beatMarkers[i].time > currentTime && beatMarkers[i].HasTag(tag))
{
return beatMarkers[i];
}
}
return null;
}
/// <summary>
/// 获取时间范围内的所有节拍标记
/// </summary>
public List<BeatMarker> GetBeatsInRange(float startTime, float endTime)
{
var result = new List<BeatMarker>();
if (beatMarkers == null) return result;
for (int i = 0; i < beatMarkers.Count; i++)
{
if (beatMarkers[i].time >= startTime && beatMarkers[i].time <= endTime)
{
result.Add(beatMarkers[i]);
}
}
return result;
}
/// <summary>
/// 按 BPM 在指定范围内一键生成等距节拍
/// </summary>
/// <param name="startTime">生成起始时间(秒)</param>
/// <param name="endTime">生成结束时间(秒)</param>
/// <param name="defaultTags">每个生成的节拍默认附带的 tags</param>
public void GenerateBeatsFromBPM(float startTime, float endTime, List<string> defaultTags = null)
{
float interval = BeatInterval;
if (interval <= 0f) return;
// 对齐起始时间到 audioStartOffset
float firstBeatTime = audioStartOffset;
while (firstBeatTime < startTime)
{
firstBeatTime += interval;
}
int beatCounter = Mathf.RoundToInt((firstBeatTime - audioStartOffset) / interval);
for (float t = firstBeatTime; t <= endTime; t += interval)
{
int bar = beatCounter / beatsPerBar;
int beatInBar = beatCounter % beatsPerBar;
var marker = new BeatMarker(
time: t,
tags: defaultTags != null ? new List<string>(defaultTags) : new List<string>(),
barIndex: bar,
beatInBar: beatInBar
);
beatMarkers.Add(marker);
beatCounter++;
}
SortBeats();
}
/// <summary>
/// 清除所有节拍标记
/// </summary>
public void ClearBeats()
{
beatMarkers.Clear();
}
/// <summary>
/// 按时间排序所有节拍标记
/// </summary>
public void SortBeats()
{
beatMarkers.Sort();
}
/// <summary>
/// 根据 BPM 和拍号自动计算小节/拍索引
/// </summary>
public void RecalculateBarIndices()
{
float interval = BeatInterval;
if (interval <= 0f) return;
for (int i = 0; i < beatMarkers.Count; i++)
{
float adjustedTime = beatMarkers[i].time - audioStartOffset;
int totalBeat = Mathf.RoundToInt(adjustedTime / interval);
beatMarkers[i].barIndex = totalBeat / beatsPerBar;
beatMarkers[i].beatInBar = totalBeat % beatsPerBar;
}
}
}
}