- Home /
Making an Array of Animations Play at Random With No Repeats
I have created a practice project to figure out how to get a random animation to play every time a button is clicked until all the animations have played with no animation playing twice. Ten animations are assigned to anims[] in the inspector. So far I have come up with this script:
var anims : AnimationClip[]; var randomAnims : AnimationClip[]; var order = new ArrayList(); var currentClip : AnimationClip; var chosenOne: int; var orderString = new ArrayList();
function OnGUI() { if (GUI.Button (Rect (105, 457, 100, 22), "Next")) { PlayAtRandom(); } }
function PlayAtRandom() { for (var i = anims.length - 1; i > 0; i--) { chosenOne = Random.Range(0,anims.length); var next = i.ToString(); var ind = orderString.IndexOf(next);
if (ind < 0)
{
orderString.Add(next);
order.Add(i);
randomAnims[chosenOne] = anims[order[i]];
currentClip = randomAnims[chosenOne];
animation.Play(currentClip.name);
}
}
}
When I press play, and press the "Next" button, it generates an error saying "ArgumentOutOfRangeException: Index is less than 0 or more than or equal to the list count." Pointing to line 26 "randomAnims[chosenOne] = anims[order[i]];".
What am I doing wrong here? Thank you.
Answer by Statement · Mar 25, 2011 at 02:39 PM
If you're willing to rewrite your code, consider this example which uses shuffling and queues. It is still possible that two animations come after each other if the first order was a, c, b and the other order is b, a, c. However you can easily modify this code to know the last used animation and swap it for the one in the other end of the array. That way you're guaranteed to never have same animation appear twice in a row.
import System.Collections.Generic; var anims : AnimationClip[]; private var queue : Queue.<AnimationClip>;
// Added for comment discussion. private var lastAnim : AnimationClip;
function OnGUI() { if (GUI.Button (Rect (105, 457, 100, 22), "Next")) PlayNext(); }
function PlayNext() { if (queue == null || queue.Count == 0) CreateQueue();
// Changed for comment discussion.
lastAnim = queue.Dequeue();
animation.Play(lastAnim.name);
}
function CreateQueue() { Shuffle(anims); Shuffle(anims); Shuffle(anims);
// Added for comment discussion.
// Swap the first and last if the first is the same as
// was used last animation.
if (anims[0] == lastAnim)
{
var last = anims.Length - 1;
var tmp = value[0];
value[0] = value[last];
value[last] = tmp;
}
queue = new Queue.<AnimationClip>(anims);
}
function Shuffle(value : AnimationClip[]) { for (var i = 0; i < value.Length; ++i) { var r = Random.Range(0, value.Length); var tmp = value[i]; value[i] = value[r]; value[r] = tmp; } }
You said, "However you can easily modify this code to know the last used animation and swap it for the one in the other end of the array."
This is exactly what I need help on! How would I go about doing that?
Yes! I do! :) Just keep a private variable that you set each time you dequeue. Then in CreateQueue you can see if the first item in the array is the one (just before creating the queue). If it is, then swap it with the last item. See the shuffle function where I swap each item with a random item how to swap a value. You need a temp value (tmp) just like I did.
This is very good. Thanks. I need to make sure that no animation that has played before ever plays again. So I guess the button text should change to "Stop" once it reaches the very end and when clicked, stops the current animation.
Yeah, you could just create a queue and run it until it's depleted. When its empty you've played all possible animations without any duplicates (assu$$anonymous$$g the array doesn't contain duplicates)
Answer by Meltdown · Mar 25, 2011 at 01:57 PM
Change the line of code that reads...
chosenOne = Random.Range(0,anims.length);
to
chosenOne = Random.Range(0,anims.length - 1);
You are trying to get a random animation, but since Random.Range takes a 0 based value, you need to put in the -1, so it will make sure you always getting a range within the bounds of the array.
Actually, Random.Range returns a random integer number between $$anonymous$$ [inclusive] and max [exclusive]. See http://unity3d.com/support/documentation/ScriptReference/Random.Range.html I was wrong about this for a long time.
Answer by Statement · Mar 25, 2011 at 02:19 PM
randomAnims[chosenOne] = anims[order[i]];
You must have arrays of inconsistent length.
You're using an index based of the length of anims but it's unclear if order is at least as long as anims, and if any value in order is less than anims.length. Finally, your randomAnims also must be at least the size of anims. Are they?
Since you seem to be adding items to order procedurally, order.Add(i);, then try to index into it order[i], and i starts as the highest index, this looks like the place you're getting errors. Consider this with literal values:
order.Add(30);
randomAnims[chosenOne] = anims[ order[30] ];
order only contains one item in the first iteration still you're trying to access the last...