List, sorting audio sources

Hi, below is script I’ve written to populate a list of enemy audio sources that are on automatic weapons, then if they are playing put them into another list and sort them by distance to the player so only have the closest xx play so not to sound horrible, what I’d like to know is this code ok or its it a expensive overhead ? Thanks.

[Header("found when the scene is loaded")]
[SerializeField] public Transform m_Cam;

[Header("maximum sound sources to play at once")]
public int _maxWeaponSoundsources;
public int _maxFootstepsSoundsources;

[Header("colours")]
public Color _redColor;
public Color _greenColor;
public Color _whiteColor;

[Header("List of AudioSource's")]
[SerializeField] public List<AudioSource> _audioSources = new List<AudioSource>();

[Header("List of automaitic weapon sound effects playing at the same time")]
[SerializeField] public List<AudioSource> _automaticWeaponAudioSources = new List<AudioSource>();

[Header("List of footstep sound effects playing at the same time")]
[SerializeField] public List<AudioSource> _footStepAudioSources = new List<AudioSource>();

private int MaxWeaponSoundsources;

// -----------------------------------------------------------------
// Name :   OnEnable
// Desc :  
// -----------------------------------------------------------------
private void OnEnable()
{
    MaxWeaponSoundsources       = _maxWeaponSoundsources - 1;

    if (GameObject.Find("AR Camera") != null)
    {
        m_Cam = GameObject.Find("AR Camera").GetComponent<Transform>();
        return;
    }

    if (GameObject.Find("Temp Camera") != null)
        m_Cam = GameObject.Find("Temp Camera").GetComponent<Transform>();
    else if (GameObject.Find("UI Camera") != null)
        m_Cam = GameObject.Find("UI Camera").GetComponent<Transform>();
}

// -----------------------------------------------------------------
// Name :   Update
// Desc :  
// -----------------------------------------------------------------
private void Update()
{

    if (AudioManager.instance == null)
        return;

    if (_audioSources.Count < MaxWeaponSoundsources)
        return;

    if (_automaticWeaponAudioSources.Count > MaxWeaponSoundsources)
        _automaticWeaponAudioSources.Sort(ByDistance);

    if (_audioSources.Count > MaxWeaponSoundsources)
    {
        for (int i = 0; i < _audioSources.Count; i++)
        {
            if (!_audioSources[i].isPlaying)
                _audioSources[i].gameObject.GetComponent<Renderer>().material.color = _whiteColor;

            if (_audioSources[i].isPlaying)
            {
                if (!_automaticWeaponAudioSources.Contains(_audioSources[i]))
                    _automaticWeaponAudioSources.Add(_audioSources[i]);

                for (int l = 0; l < _automaticWeaponAudioSources.Count; l++)
                {
                    _automaticWeaponAudioSources[l].gameObject.GetComponent<Renderer>().material.color = _greenColor;

                    if (l > MaxWeaponSoundsources)
                    {
                        // MUTE the sound source so the weapon can NOT be heard
                        _automaticWeaponAudioSources[l].mute = true;
                        _automaticWeaponAudioSources[l].gameObject.GetComponent<Renderer>().material.color = _redColor;
                    }
                    else
                    {
                        // DONT mute the sound source so the weapon CAN be heard
                        _automaticWeaponAudioSources[l].mute = false;
                        _automaticWeaponAudioSources[l].gameObject.GetComponent<Renderer>().material.color = _greenColor;
                    }

                    // this MUST be done last in this for() loop, if NOT 'isPlaying' anymore remove from list
                    if (!_automaticWeaponAudioSources[l].isPlaying)
                    {
                        _automaticWeaponAudioSources[l].mute = false;
                        _automaticWeaponAudioSources.Remove(_automaticWeaponAudioSources[l]);
                    }
                }
            }
        }
    }
}

// -----------------------------------------------------------------
// Name :   ByDistance
// Desc :  
// -----------------------------------------------------------------
private int ByDistance(AudioSource a, AudioSource b)
{
    if (GameSceneManager.instance.player == null)
        return 0;

    float dstToA = Vector3.Distance(GameSceneManager.instance.player.position, a.transform.position);
    float dstToB = Vector3.Distance(GameSceneManager.instance.player.position, b.transform.position);

    return dstToA.CompareTo(dstToB);
}

Go to Window → Analysis → Profiler and check for yourself. Remember, readings in the editor have nothing to do with how it will perform on actual hardware (eg, a PC or iOS or Android or XBox or whatever). Always test on the target hardware if you are concerned.

https://docs.unity3d.com/Manual/Profiler.html

1 Like

One thing that I notice is that you are looping forwards through a list, and you are potentially removing elements from that list while you loop through it.

What removing an element will do, is it will change the indexes of every element lower down on the list, and you will skip elements in your list when you try to fetch the next element.

public class TestListLoop : MonoBehaviour
{
    private List<int> nums = new List<int>();
   
    void Start()
    {
        //Adds 0,1,2,3,4 to a list
        for (int x = 0; x < 5; x++)
        {
            nums.Add(x);
        }

        //prints 0,2,4 from the list
        for (int n = 0; n < nums.Count; n++)
        {
            Debug.Log(nums[n]);
            nums.Remove(n);
        }
    }
}

You can remove the current element from your list while looping backwards without changing the indexes of the elements that you haven’t looped yet.

        //prints 4,3,2,1,0 from the list
        for (int n = nums.Count-1; n >= 0; n--)
        {
            Debug.Log(nums[n]);
            nums.Remove(n);
        }

But it is considered bad practice to remove elements from a collection while you are looping through it, regardless of how you do it.

1 Like

Looping backwards probably has better performance, but I generally just make a copy of the collection and loop over the copy while modifying the original. List.ToArray() is pretty handy for this.

2 Likes

I’m curious why you’re using a main list as opposed to having the enemies just monitor their distance to the player and mute/unmute accordingly? Both ways can work, just wondering.

Having a lot play at once can cause clipping, so volume is a factor. If it becomes an issue, you could track how many are playing at a time and adjust accordingly. Also you’ll want to try careful listening on mobile devices and possibly tweak their DSP settings if you get audio dropouts caused by too many sounds playing at once. I ran into this with a note-based app I’ve been working on, and making the sounds shorter and tweaking Android’s DSP settings helped a lot—though the buffer size tweak was in the opposite direction I expected…making it smaller caused less dropouts, and the reverse was true for PC (?).

Also you can use PlayOneShot(), I seem to remember it’s better for lots of sounds at once, maybe someone can speak to that.

2 Likes

Hi, thanks for the reply, you say ‘having the enemies just monitor their distance to the player and mute/unmute accordingly’ wouldn’t this also need a List/Array for the enemy to loop through to see if they are one of the closest xx in the List/Array, I’ll look into the DSP settings, cheers.

1 Like

You’d need a list to see if they were the closest, but I thought it was more based on absolute distance, so that would just be each enemy individually. Not sure how you want it set up exactly though. As always, several ways to do things, pros and cons to each…

I only tweaked the DSP buffer size and the number of voices (as there were a lot of overlapping sounds). It did make a noticeable difference on Android at least. YMMV :wink:

1 Like