- Home /
Never repeat last random audioclip
The code below is merely a snippet of the larger script, I'm curious if there is a way to not repeat the last audioclip. As I'd like the footstep audio to be randomised yet not having you hear the same clip again.
Any help would be greatly appreciated!
var sounds : AudioClip[];
function Update()
{
if(grassTimer == 0)
{
audio.clip = sounds [Random.Range(0,sounds.length)];
audio.Play();
}
}
Answer by Hoeloe · Feb 09, 2015 at 01:02 PM
I think taxvi has misunderstood your question. You want to select a random sound to play each time, but not use the same one twice, correct?
The problem is, as I'm sure you're aware, that you're selecting from a list of all your sounds, including the one that was just played. What you want to do is select from a list of all sounds except the one that was just played. You could do this by selecting a random sound, checking if it is the one you just played, and if so selecting another, but this is bad practice as it has a small chance of locking up your program.
If you were using C#, I'd tell you to use a library called Linq for this, which allows all sorts of useful array operations, including one that can filter out array elements.
Since you're using Javascript (and I heavily recommend you switch to C# when you can. Javascript may look easier, but it hides a lot of the complexity, which can make problems occur more easily), you can't do this, but you can make your own filter function, somewhat like this:
function FilterCurrentClip()
{
var rtrn : AudioClip[] = new AudioClip[sounds.Length - 1];
var j : int = 0;
for(var i : int = 0; i < sounds.Length; i++)
{
if(sounds[i] != audio.clip)
{
rtrn[j] = sounds[i];
j ++;
}
}
return rtrn;
}
This takes the existing array, and creates a new one from the values, skipping the element that you don't want to use.
Now, when you want to set the new clip, you can do this:
var filteredSounds = FilterCurrentClip();
audio.clip = filteredSounds[Random.Range(0,filteredSounds.Length)];
This is quite a slow operation, and C#'s Linq would be a lot faster, but it should work (as long as your sounds array always contains the value of audio.clip, otherwise it will break).
Thanks for the suggestion, I'm trying my best to get my head around this.
I get this error:
IndexOutOfRangeException: Array index is out of range.
Problem on line:
rtrn[j] = sounds[i]
and
var filteredSounds = FilterCurrentClip();
That's the error you'll get if the sound clip isn't contained in the sounds array. If your clip is initially empty, this will also happen. You can do a precursor check to see if the sounds array contains the current clip value, and just return the complete array if it doesn't.
I understand it's not best to spoon feed people but how do I make a check?
Isn't this our check? if(sounds[i] != audio.clip)
$$anonymous$$y sounds array does contain two clips at runtime. Sorry if these are stupid questions.
That's our check to see if the current element in sounds is not equal to the current clip. The problem is that arrays are fixed length, and I'm making the assumption that the new array will always filter exactly one element from the sounds array. If, however, the current clip (that's the one attached to the AudioSource) is not a clip from the sounds array, then the check you've highlighted sounds[i] != audio.clip
will always be true, so we'll be trying to copy over every element from the sounds array into another array that is too small to hold them. So, ins$$anonymous$$d, we need to make sure that the array will always be big enough.
Notice how, to rebuild the array, I'm having to loop over all the elements and copy them in? Well, you'll have to do that to make the check. However, that requires you to loop over the array twice, which is generally not a good idea. You can, however, be a little clever, and merge the two loops together with a different check.
Ins$$anonymous$$d of looping through, and checking each element in the array against your current one (which is what I was originally suggesting), you could ins$$anonymous$$d check in the main loop to see if there are too many audio clips to store in the array, and if there are, just return the main array. This requires just a simple test to see if j (the index into the new array) is greater than or equal to the length of the array (arrays are 0-indexed, so the last element in the array is at array index length-1).
function FilterCurrentClip()
{
var rtrn : AudioClip[] = new AudioClip[sounds.Length - 1];
var j : int = 0;
for(var i : int = 0; i < sounds.Length; i++)
{
if(j >= rtrn.Length)
{
return sounds;
}
if(sounds[i] != audio.clip)
{
rtrn[j] = sounds[i];
j ++;
}
}
return rtrn;
}
I suggest you read up about for loops and arrays. There's a nice answer here that explains them in detail: http://answers.unity3d.com/questions/548266/searching-through-an-array.html
It works well, not perfect but I'm happy with this. It plays the sounds and on the odd occasion it will play the last played audioclip. It's far better than it was, for my purposes this is great. Thanks for your time.