244 lines
7.6 KiB
C#
244 lines
7.6 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|
||
}
|