Problem with random music script
Hi, I have a problem with my music manager script, I want to play random songs that are in a list but to avoid to play them twice in a row, I add them to another list and remove them from the first one, until that one is empty,
I have an error with the line 71 NullReferenceException: Object reference not set to an instance of an object when I start the game.
Now, that list is effectively empty if the if statement is not taken into account, but the if statement should be taken into account and the list should be filled with the songs added every time a new one is played, so I don't really understand my mistake here, nor how to fix it..
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MusicManager : MonoBehaviour
{
private AudioSource audioSource;
//The public list containing all the songs to be played
public List<AudioClip> musicTracksList;
//The private, empty list that will contain the songs that have been played
List<AudioClip> musicTracksPlayed;
private void Start()
{
audioSource = GetComponent<AudioSource>();
}
//For the main menu, play the first track in the list / That works fine
public void PlayMusicMainMenu()
{
audioSource.loop = true;
audioSource.clip = musicTracksList[0];
audioSource.Play();
}
//Function engaged by the Game Manager when the game starts / The RandomTrack doesn't work
public void PlayGameMusic()
{
audioSource.clip = RandomTrack();
audioSource.Play();
float trackLength = audioSource.clip.length;
StartCoroutine(NextSong(trackLength));
}
/*Find a random track to be played for the PlayGameMusic
* If there are tracks in the music tracks list, pick a random one, return it as the audio clip to be played,
* remove it from the music tracks list and add it to the played tracks list.
* When there are no more tracks in the music track list, put all the tracks from the played tracks list in
* the music track list and re-do the random picking with the previous steps.
* Also, clear the played track list so it's empty again.
*
* Unity says there is an error with the line 71 NullReferenceException: Object reference not set to an instance of an object */
AudioClip RandomTrack()
{
Debug.Log("La liste principale est elle vide ? " + musicTracksList==null);
if (musicTracksList.Count > 0)
{
int RandomTrackID = Random.Range(0, musicTracksList.Count);
AudioClip TrackToPlay = musicTracksList[RandomTrackID];
musicTracksList.Remove(TrackToPlay);
musicTracksPlayed.Add(TrackToPlay);
return TrackToPlay;
}
else
{
musicTracksList = musicTracksPlayed;
musicTracksPlayed.Clear();
int RandomTrackID = Random.Range(0, musicTracksList.Count);
AudioClip TrackToPlay = musicTracksList[RandomTrackID];
musicTracksList.Remove(TrackToPlay);
musicTracksPlayed.Add(TrackToPlay);
return TrackToPlay;
}
}
//Coroutine to play the next track when the one playing is over.
IEnumerator NextSong(float trackDuration)
{
yield return new WaitForSeconds(trackDuration);
PlayGameMusic();
}
}
Thank you for your inputs.
Maybe the list isn't initialized?
Try changing
public List<AudioClip> musicTracksList;
to
public List<AudioClip> musicTracksList = new List<AudioClip>();
Hi,
Thank you for your answer, so I try to do that for both my list, now I'm getting an out of range error .. ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
I have narrow it down to that line, line 62 in my original script
AudioClip TrackToPlay = musicTracksList[RandomTrackID];
What I don't understand, is that the RandomTrackID is a random int obtained in the line before :
int RandomTrackID = Random.Range(0, musicTracksList.Count);
So it should not be out of range or negative like the errors says,
I've tried several things :
-if I debug.log the value of the RandomTrackID, I always get 0.
I copied the random part in my main menu part, this original one :
public void PlayMusicMainMenu() { audioSource.loop = true; audioSource.clip = musicTracksList[0]; audioSource.Play(); }
So instead of getting the track at index 0, I get a random one, it's working perfectly as intended.
If, instead of using a list, I'm using an array like I did before, it's working, but the problem is that I need to change the size of it at runtime, hence why I try to use list.
Also and I think this the most important, if in my RandomTrack method, I just remove all the ramdomness part and just say to my method to just return an audioclip at a given index that I know it exist, like this :
AudioClip RandomTrack()
{
//int RandomTrackID = Random.Range(0, musicTracksList.Count);
//Debug.Log(RandomTrackID);
AudioClip TrackToPlay = musicTracksList[2];
//musicTracksList.Remove(TrackToPlay);
//musicTracksPlayed.Add(TrackToPlay);
return TrackToPlay;
}
It still throws me the out of range error, so now I'm wondering if it's more a problem with the return function itself ? But why would it work with arrays and not lists.....?
Also, if I try to assign a specific index in my PlayGameMusic method just like I did in the PlayMainMenu method, it gives me the error of out of range, which is weird because the PlayMainMenu method is working fine, I tried to remove the coroutine part, it stills gives that error. I'll try later by changing list to ArrayList to see if that can solve the problem.
//For the main menu, play the first track in the list / That works fine
public void PlayMusicMainMenu()
{
audioSource.loop = true;
audioSource.clip = musicTracksList[0];
audioSource.Play();
}
//Function engaged by the Game Manager when the game starts / The RandomTrack doesn't work
public void PlayGameMusic()
{
audioSource.clip = musicTracksList[2];
audioSource.Play();
float trackLength = audioSource.clip.length;
StartCoroutine(NextSong(trackLength));
}
Answer by jdg23 · Apr 20, 2021 at 03:21 PM
Found a solution to this issue. The first mistake was that the lists were not initialized, thanks @SpaceManDan Then, it would throw an out of range issue. That was due to not adding tracks from musicTracksPlayed to musicTrackList with a foreach loop.
It works fine now.
Here is the final script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MusicManager : MonoBehaviour
{
private AudioSource audioSource;
public List<AudioClip> musicTracksList = new List<AudioClip>();
List<AudioClip> musicTracksPlayed = new List<AudioClip>();
private void Start()
{
audioSource = GetComponent<AudioSource>();
}
public void PlayMusicMainMenu()
{
audioSource.loop = true;
audioSource.clip = musicTracksList[0];
audioSource.Play();
}
public void PlayGameMusic()
{
audioSource.clip = SetMusicToPlay();
audioSource.Play();
Debug.Log(audioSource.clip);
float trackLength = audioSource.clip.length;
StartCoroutine(NextSong(trackLength));
}
AudioClip SetMusicToPlay()
{
if (musicTracksList.Count > 0)
{
int trackID = Random.Range(0, musicTracksList.Count);
AudioClip trackToPlay = musicTracksList[trackID];
musicTracksPlayed.Add(trackToPlay);
musicTracksList.Remove(trackToPlay);
Debug.Log("Music Tracks Played has " + musicTracksPlayed.Count + " elements");
Debug.Log("Music Tracks List has " + musicTracksList.Count + " elements");
return trackToPlay;
}
else
{
Debug.Log("Engage the copy from the played track list to the track list");
CopyToTracksList();
int trackID = Random.Range(0, musicTracksList.Count);
AudioClip trackToPlay = musicTracksList[trackID];
musicTracksPlayed.Clear();
musicTracksPlayed.Add(trackToPlay);
musicTracksList.Remove(trackToPlay);
Debug.Log("Music Tracks List has " + musicTracksList.Count + " elements");
Debug.Log("Music Tracks Played has " + musicTracksPlayed.Count + " elements");
return trackToPlay;
}
}
private void CopyToTracksList()
{
foreach(AudioClip trackToAdd in musicTracksPlayed)
{
musicTracksList.Add(trackToAdd);
}
}
IEnumerator NextSong(float trackDuration)
{
yield return new WaitForSeconds(trackDuration);
PlayGameMusic();
}
}
Hey, I havn't had time to read over all the code you posted earlier but I think maybe another issue you may have been having was actually that list.count gives you the total number of items. Not the exact index.
For example:
public List<int> myList = new List<int>();
void Start()
{
myList.Add(1);
myList.Add(2);
myList.Add(3);
print(myList.Count);
}
The result will be 3 however the index is like so... index 0 = 1, index 1 = 2, index 2 = 3. For example try printing myList[3] (which would actually be the 4th entry because zero counts as one unit.) You will get the OurOfRange exception. This was an issue the greeks ran into and then invented zero to solve the problem. Wikipedia Zero
So when using List.Count you need to subtract 1 most of the time to get the right index. It all depends on how you query the list as if your query index follows the same architecture you wont have an issue.
so an example to avoid this would look like this,
if(myList.count - 1 == indexImLookingFor)
{
Dostuff();
}
That being said, the way you are doing it now is perfect, you load the list and query the correct index.
Hi,
Thanks for your answer, I appreciate your help on the initialization, that solved my issue.
For the out of range, it was due to not using a foreach loop to copy the content of the second list to the first, my mistake was that I made them equal so when I cleared the second list, the first one got cleared too as they were equal. The foreach loop and add cleared that issue.
There is no need to put a -1 when using Random.Range with int as with int , the max is exclusive, that's why I did not put it.
My best and thanks again for your help
Indeed, glad you already knew about inclusive/exclusive and glad you got your lists worked out.
Good luck out there.