解决重复切换State的时候会导致重复播放对应Segment的问题。

修复FadeIn读取了FadeOut参数的问题。
增加Initial Delay功能。
重构AudioScheduler.ConfigureSource() -> SetupSource(), RegisterActiveSound(), StartPlayBack()。
移动长音频相关功能至LongAudio文件夹。
This commit is contained in:
2026-03-25 17:01:38 +08:00
parent 0f7e286206
commit e032f7687f
19 changed files with 307 additions and 74 deletions
@@ -0,0 +1,147 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace OCES.Audio
{
public class AudioContainerSelector
{
// TODO: 根据 audioObject.ContainerScope 控制随机历史的共享范围:
// - Global(当前实现):同一 AudioObject 的所有并发实例共享同一份 playHistory 和 limitRepetition 队列,
// 不放回随机在全局层面生效,并发实例之间互相感知已播放记录。
// - PerInstance:每个并发播放实例持有独立的 playHistory 和 limitRepetition 队列,
// 不放回随机仅在该实例内生效,实例之间完全隔离。
// 实现思路:PerInstance 模式下将 HashSet/Queue 作为局部变量传入协程,不再写入这两个 Dictionary。
readonly Dictionary<uint, HashSet<int>> m_randomPlayedHistories = new();
readonly Dictionary<uint, Queue<int>> m_randomRecentQueues = new();
readonly Dictionary<uint, int> m_sequenceNextIndex = new();
/// <summary>
/// Shuffle 随机模式下选取 index(支持 LimitRepetition 队列)
/// </summary>
public int PickShuffleIndex(AudioObject audioObject)
{
int count = audioObject.Name.Count;
// LimitRepetition 队列
int limitRepetition = Mathf.Clamp(audioObject.LimitRepetition, 0, count - 1);
if (!this.m_randomRecentQueues.TryGetValue(audioObject.Id, out Queue<int> recent))
{
recent = new Queue<int>();
this.m_randomRecentQueues[audioObject.Id] = recent;
}
if (!audioObject.RandomType)
{
List<int> candidates = Enumerable.Range(0, count)
.Where(i => !recent.Contains(i))
.ToList();
if (candidates.Count == 0)
candidates = Enumerable.Range(0, count).ToList();
int chosen = candidates[Random.Range(0, candidates.Count)];
if (limitRepetition > 0)
{
recent.Enqueue(chosen);
if (recent.Count > limitRepetition)
recent.Dequeue();
}
return chosen;
}
// 不放回随机
if (!this.m_randomPlayedHistories.TryGetValue(audioObject.Id, out HashSet<int> history))
{
history = new HashSet<int>();
this.m_randomPlayedHistories[audioObject.Id] = history;
}
List<int> available = Enumerable.Range(0, count)
.Where(i => !history.Contains(i) && !recent.Contains(i))
.ToList();
if (available.Count == 0)
{
history.Clear();
available = Enumerable.Range(0, count)
.Where(i => !recent.Contains(i))
.ToList();
if (available.Count == 0)
available = Enumerable.Range(0, count).ToList();
}
int chosenIndex = available[Random.Range(0, available.Count)];
history.Add(chosenIndex);
if (limitRepetition > 0)
{
recent.Enqueue(chosenIndex);
if (recent.Count > limitRepetition)
recent.Dequeue();
}
return chosenIndex;
}
/// <summary>
/// 连续容器的 Random 模式下,带 LimitRepetition 逻辑地选取 index
/// </summary>
public int PickNextRandomIndex(AudioObject audioObject, List<int> remainingPool, Queue<int> recentPlayed, int limitRepetition)
{
if (!this.m_randomPlayedHistories.TryGetValue(audioObject.Id, out HashSet<int> history))
{
history = new HashSet<int>();
this.m_randomPlayedHistories[audioObject.Id] = history;
}
List<int> candidates = remainingPool
.Where(i => !recentPlayed.Contains(i))
.ToList();
if (candidates.Count == 0)
candidates = new List<int>(remainingPool);
int index = candidates[Random.Range(0, candidates.Count)];
if (audioObject.RandomType)
remainingPool.Remove(index);
if (limitRepetition > 0)
recentPlayed.Enqueue(index);
if (recentPlayed.Count > limitRepetition)
recentPlayed.Dequeue();
history.Add(index);
return index;
}
/// <summary>
/// 获取 Sequence 模式下的当前 index 并推进游标
/// </summary>
public int GetNextSequenceIndex(AudioObject audioObject)
{
int current = this.m_sequenceNextIndex.GetValueOrDefault(audioObject.Id, 0);
this.m_sequenceNextIndex[audioObject.Id] = (current + 1) % audioObject.Name.Count;
return current;
}
public void ResetHistory(uint id)
{
if (this.m_randomPlayedHistories.TryGetValue(id, out HashSet<int> history))
{
history.Clear();
}
}
public int GetHistoryCount(uint id)
{
return this.m_randomPlayedHistories.TryGetValue(id, out HashSet<int> history) ? history.Count : 0;
}
}
}