I wrote another one that handles hierarchies with duplicated names and sub-branches.
using System;
using System.Collections.Generic;
using UnityEngine.Audio;
public static class AudioMixerGroupExtensions
{
/// <summary>
/// This method resolves the 'Hierarchy' of the AudioMixer's AudioMixerGroups.
/// It resolves the correct hierarchy even if AudioMixerGroup names or even
/// whole branches are duplicated. It can do this because AudioMixer.FindMatchingGroups returns
/// it's results in a depth-first order.
/// </summary>
/// <param name="mixer">The <see cref="AudioMixer"/> to resolve the hierarchy for.</param>
/// <returns>A AudioMixerGroupHierarchy</returns>
public static IAudioMixerGroupHierarchy GetMixerGroupHierarchy(this AudioMixer mixer)
{
return AudioMixerGroupHierarchy.ResolveHierarchy(mixer);
}
private class AudioMixerGroupHierarchy : IAudioMixerGroupHierarchy
{
private readonly AudioMixerGroupHierarchy parent;
private readonly AudioMixerGroup mixerGroup;
private readonly List<AudioMixerGroupHierarchy> children;
private readonly string path;
private readonly int depth;
private AudioMixerGroupHierarchy(AudioMixerGroup mixerGroup, AudioMixerGroupHierarchy parent, int depth)
{
this.mixerGroup = mixerGroup;
this.parent = parent;
this.depth = depth;
path = $"{parent?.path}/{mixerGroup.name}";
children = new List<AudioMixerGroupHierarchy>();
}
internal static IAudioMixerGroupHierarchy ResolveHierarchy(AudioMixer mixer)
{
// We are relying on the fact that FindMatchingGroups returns
// the mixers in a depth-first order, meaning as we iterate
// the list we will never see an element whose parent we have
// not seen before
var depthFirstOrderedMixerGroups = mixer.FindMatchingGroups("");
var root = new AudioMixerGroupHierarchy(depthFirstOrderedMixerGroups[0], null, 0);
var ancestorStack = new Stack<AudioMixerGroupHierarchy>();
ancestorStack.Push(root);
// Iterate over all mixerGroups, resolving the hierarchy by
// testing results of mixer.FindMatchingGroups(searchPath)
// against the mixerGroup we are iterating on.
// Skip the first element which is the root.
for (var i = 1; i < depthFirstOrderedMixerGroups.Length; i++)
{
var subMixer = depthFirstOrderedMixerGroups*;*
RETEST_ELEMENT:
var parent = ancestorStack.Peek();
var searchPath = $“{parent.path}/{subMixer.name}”;
var searchPathResults = mixer.FindMatchingGroups(searchPath);
var index = Array.IndexOf(searchPathResults, subMixer);
if (index < 0)
{
// If the entry was not found in this searchPath, then search in the previous parent
ancestorStack.Pop();
// Debug.LogWarning($“Entry not at {searchPath}, back-track to previous parent {ancestorStack.Peek().Path} and re-iterate”);
goto RETEST_ELEMENT; // ← WARNING GOTO
}
var isProbableNode = searchPathResults.Length != 1;
// A LEAF may be falsely detected as a NODE:
// If the path of this node is a partial path of another deeper path
// there is no way to verify it is a LEAF before the whole tree has been resolved
// The only side-effect is an extra iteration, where we test the leaf-node
// for children before back-tracking.
var node = new AudioMixerGroupHierarchy(subMixer, parent, ancestorStack.Count);
parent.children.Add(node);
if (isProbableNode)
{
ancestorStack.Push(node);
}
}
return root;
}
IAudioMixerGroupHierarchy IAudioMixerGroupHierarchy.Parent => parent;
AudioMixerGroup IAudioMixerGroupHierarchy.Group => mixerGroup;
IReadOnlyList IAudioMixerGroupHierarchy.Children => children;
int IAudioMixerGroupHierarchy.Depth => depth;
string IAudioMixerGroupHierarchy.Path => path;
}
}
public interface IAudioMixerGroupHierarchy
{
public IAudioMixerGroupHierarchy Parent { get; }
public AudioMixerGroup Group { get; }
public IReadOnlyList Children { get; }
public int Depth { get; }
public string Path { get; }
}