using System.Collections.Generic;
using AK.Wwise;
using Sirenix.OdinInspector;
using UnityEngine;
using Event = AK.Wwise.Event;
namespace Cielonos.MainGame
{
///
/// 音乐谱面数据容器,存储 BGM 元信息和手动/自动生成的节拍标记
///
[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 beatMarkers = new List();
///
/// 单拍间隔时间(秒)
///
public float BeatInterval => 60f / bpm;
///
/// 单小节时长(秒)
///
public float BarDuration => BeatInterval * beatsPerBar;
///
/// 获取指定时间之前的最后一个节拍标记
///
///
///
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;
}
///
/// 获取最接近指定时间的节拍标记
///
/// 目标时间(秒)
/// 最大容差(秒),超出此范围返回 null
/// 最近的节拍标记,若超出容差则返回 null
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;
}
///
/// 获取指定时间之后最近的节拍标记
///
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;
}
///
/// 获取指定时间之后最近的、带有特定 tag 的节拍标记
///
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;
}
///
/// 获取时间范围内的所有节拍标记
///
public List GetBeatsInRange(float startTime, float endTime)
{
var result = new List();
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;
}
///
/// 按 BPM 在指定范围内一键生成等距节拍
///
/// 生成起始时间(秒)
/// 生成结束时间(秒)
/// 每个生成的节拍默认附带的 tags
public void GenerateBeatsFromBPM(float startTime, float endTime, List 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(defaultTags) : new List(),
barIndex: bar,
beatInBar: beatInBar
);
beatMarkers.Add(marker);
beatCounter++;
}
SortBeats();
}
///
/// 清除所有节拍标记
///
public void ClearBeats()
{
beatMarkers.Clear();
}
///
/// 按时间排序所有节拍标记
///
public void SortBeats()
{
beatMarkers.Sort();
}
///
/// 根据 BPM 和拍号自动计算小节/拍索引
///
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;
}
}
}
}