Random Motion



The Basics

Randomness is one of the most asked about uses of expressions and After effects gives us a pretty good arsenal of tools with which to implement it. The basic random() method has several different flavors. Some examples would probably be helpful in understanding the different ways you can call random():

Random Expression               Result
random() // number between 0 and 1 random(6) // number between 0 and 6 random(-2,4) // number between -2 and 4 random([3,4,5]) // vector between [0,0,0] and [3,4,5] random([3,4,5],[6,7,8]) // vector between [3,4,5] and [6,7,8]

So you can see that the random() method is pretty flexible.

We'll illustrate how you might use this with a few examples. Let's say you wanted to randomly position a layer within the comp. Any of these expressions for Position will do the trick:


random([thisComp.width,thisComp.height])

random([0,0],[thisComp.width,thisComp.height])

x = random(thisComp.width);
y = random(thisComp.height);
[x,y]

x = random()*thisComp.width;
y = random()*thisComp.height;
[x,y]

While the expressions above do indeed position the layer randomly, if you try any them you'll discover what seems to be a serious limitation of the random() method. You get a different result on each frame. This is great if you want to have a layer frenetically hop all over the place. Most of the time though, you want a more controlled randomness. This is where seedRandom() comes in.

Seed Random to the Rescue

seedRandom() is a clever method that Adobe came up with to let us control the rate of randomness. Actually it has more than one use. You can also use it just to change the sequence of random numbers generated by random(). seedRandom() has two parameters. The first one is the random seed, and the second is a true/false flag that tells After Effects whether or not the random numbers generated by random() will be "timeless". We'll get back to that "timeless" business in a minute, but first let's look at the other parameter. In its simplest form, seedRandom just seeds the random number generator to produce a particular sequence of random numbers that will change on every frame.

As an example, these two expressions will generate completely different sequences of random numbers (which will change on each frame):


seedRandom(3);
random()

seedRandom(999);
random()

OK - here comes the cool part. By setting the second parameter of seedRandom() to "true", the numbers generated by random() will be the same on every frame. Here's an example expression for Position that will move a layer to a random position and hold it at that location:


seedRandom(3,true);
random([thisComp.width,thisComp.height])

Remember that without the seedRandom() call, the layer would bounce around to a different Position on each frame. How does that help us? Having a layer move to a random Position and stay there is pretty boring, right? Yes it is. But all we need to do to get a new position is change the seed. So to get the layer to move around randomly in an orderly fashion, all we have to do is update the seed periodically and get a new random Position from random(). Let's construct an example. Say we want our layer to move to a new random Position every half second. All we need is some new code in our expression that will change the seed every half second. How would we do that? Remember that the Math.floor() JavaScript function rounds down to the nearest integer. We can use that to generate a seed that changes every half second by dividing the current time by .5 and rounding down to the nearest integer. Here's an example (you would change the value of "holdTime" to change the length of time each Position is held):


holdTime = .5; //time to hold each position (seconds)

seed = Math.floor(time/holdTime);
seedRandom(seed,true);
random([thisComp.width, thisComp.height])

Now the layer still moves around randomly, but it does it a half-second intervals. Let's make a modification so that our motion stays in the "title safe" area of the comp. We'll do this by restricting the results of the calls to random() to the values between 10% and 90% of the width and height of the comp. Here's the modified code:

random position every .5 seconds


holdTime = .5; //time to hold each position (seconds)
minVal = [0.1*thisComp.width, 0.1*thisComp.height];
maxVal = [0.9*thisComp.width, 0.9*thisComp.height];

seed = Math.floor(time/holdTime);
seedRandom(seed,true);
random(minVal,maxVal)

(Note that there's a slightly simpler way to do this by using the posterizeTime() method in conjunction with seedRandom(), but we'll look at that when we delve into using expressions to manipulate time).

OK, that's better, but what if we wanted the layer to move smoothly from one Position to the other? This brings us to one of the most important concepts for generating random motion, which is that whenever you set the seed to a particular number, the random sequence generated by random() will always be the same. For example if we set the seed to 1, random() might give us .45633. If we change the seed to 2, we might get .78341. But if we change the seed back to 1, we will get .45633 again. So how does this help us generate smooth random motion? Let's look again at our example above. For the first half second, the seed calculates out to be 0. So for every frame in the first half second, the call to random() generates the same random Position based on a seed of 0. After the first half second, the seed changes to 1 and the random Position changes. So what if we were to "peek ahead" by changing the seed to the value that we'll be using in the next half second to see what the next Position will be, and start heading there? The sequence would be to calculate the seed for this half second, use that to get the random Position for this half second (which will be our starting Position), bump the seed by one and use that to see what the random starting Position for the next half second will be (which we'll use as the ending Position for the current half-second segment). OK, here's the code:

random motion - synchronized


segDur = .5;// duration of each "segment" of random motion
minVal = [0.1*thisComp.width, 0.1*thisComp.height];
maxVal = [0.9*thisComp.width, 0.9*thisComp.height];

seed = Math.floor(time/segDur);
segStart = seed*segDur;
seedRandom(seed,true);
startVal =  random(minVal,maxVal);
seedRandom(seed+1,true);
endVal = random(minVal,maxVal);
ease(time,segStart,segStart + segDur, startVal, endVal);

You'll notice in the demo movie we've now got multiple stars, all at different random positions. They all have identical copies of the random position expression, so you might be wondering why they have different positions. It turns out that for any given random seed, the random values generated will be unique to each layer. Internally After Effects somehow modifies the seed so that it is unique for each layer, comp, property, effect, etc. Most of the time this is a very handy feature, but there are some occasions where it would be nice to be able to force two different layers to generate the same random numbers.

Anyway, to recap, the seed is derived from the time and the segment duration and becomes our "segment number" where segment 0 starts at time = 0, segment 1 starts at time = .5, and so on. Once we have the seed, we seed the random number generator with it and call random() to get the starting Position for this segment. Then we bump the seed, seed the random number generator with the new seed and call random() again to get the starting Position of the next segment (which is, of course, the ending Position for the current segment). Then we just use ease() to interpolate between the two Positions so that the layer eases from one Position to the next. You can change the interpolation from ease() to linear() for a different effect.

So that's all you need to generate basic random motion in After Effects. Ah, if only life were so simple.

If you duplicate the layer several times and preview the comp, you'll see that the layers moves to different Positions, but the movement of all the layers is synchronized. That is, they all arrive at their new destinations at the same time. That can be a useful effect, but what if you want something more chaotic? We'll look at a couple of things you can do to fix this.

One solution is pretty simple and gives results that can be quite useful. The other solution is much more complex (in both code and theory) but really blows the doors off this thing as far as opening up endless possibilities for randomness in After Effects. First the simple fix. We can modify the expression so the duration used by each layer is a random number within a given range, and would be different for each layer. Here's the modified code to do that:

random motion - more chaotic


segMin = .3; //minimum segment duration
segMax = .7; //maximum segment duration
minVal = [0.1*thisComp.width, 0.1*thisComp.height];
maxVal = [0.9*thisComp.width, 0.9*thisComp.height];

seedRandom(index,true);
segDur = random(segMin, segMax);
seed = Math.floor(time/segDur);
segStart = seed*segDur;
seedRandom(seed,true);
startVal =  random(minVal,maxVal);
seedRandom(seed+1,true);
endVal = random(minVal,maxVal);
ease(time,segStart,segStart + segDur, startVal, endVal);

As you can see, the only change from the previous version is the code at the beginning that seeds the random number generator with the layer's index and then calls random() to get the segment duration. This simple change can add quite a bit of chaos to the mix.

However, if you look closely at the motion, you begin to notice that each movement of any given layer always takes the same amount of time. What if what you really want is for each movement to take a random amount of time? This brings us to a major roadblock and we have to come up with a new and very powerful concept to get around it. Before we solve it, we need to take a little side trip.

Expressions Have No Memory

The implementation of expressions in After Effects is stateless. Here's the good news: that's what allows you to move the time marker to any frame in the comp and After Effects can immediately begin to show you that frame because it doesn't have to know what any expressions applied have done in the past. So at any given frame After Effects just calculates the expression and applies the results to the value that the property would have without the expression (i.e. the original value of the property plus the effect of any keyframes.) The implications of this are enormous. An example here might help.

Let's say that you wanted to add a random value from 0 to 10 degrees to the Rotation of a layer on each frame. You would expect sporadic movement but you might also expect the layer's Rotation to always be increasing in the clockwise direction. The code would seem to be simple enough. It seems like this should do the trick:

random rotation - failed attempt


rotation + random(10)

When we look at the result though, it's clear that After Effects is not accumulating the effects of the random() function from frame to frame. At each frame it starts anew with the original value of Rotation (which is 0) and adds the random number to that. This is clearly not what we wanted or expected. So what do we do?

Unfortunately, what we often have to do in situations like this is to do the frame-by-frame accumulation ourselves, with code that we add to the expression. In other words, our expression has to start at frame zero and recalculate everything that has happened since then. To accomplish this we'll have to use some JavaScript to construct a "while" loop that goes through the comp one frame at a time and adds up the random numbers. The code for the loop looks a little complex, but after you do it a few times and you start to get a feel for what's going on it gets easier. Here's what the code looks like:

random rotation with accumulating loop


j = 0;     //initialize loop counter
accum = 0; //initialize random accumulator
seedRandom(index,true)

while (j < time){
  accum += random(10);
  j += thisComp.frameDuration;
}

rotation + accum

Now we're getting what we expected. This is accomplished by the "while" loop that loops through the code between the curly braces until the variable "j" is greater than the current time. Each time through the loop, j gets incremented by one frame's worth of time (thisComp.frameDuration). When the loop finishes, the variable "accum" contains the sum of all the random numbers generated for this and all previous frames. Even though the code is not extremely complicated, all this comes at a cost. At each frame, the loop has to revisit all previous frames. As your comp increases in length, render times can begin to bog down as the expression has more and more to do on each frame. So there are practical limitations on the use of this technique, but for most situations (short comps) it will work just fine. In fact, as you will see shortly, many applications of this technique don't require a one-frame granularity and are much less expensive in terms of render time. What i mean is that you end up looping through the comp in chunks of time much larger than a single frame, which extends the usefulness of the technique even further. You'll see.


Back to the Task at Hand

OK - let's get back to the problem of random durations for our random motion. In the process we'll end up using the loop-through-the-comp technique that we just investigated. Let's go over what we need to do. We want our layer to move smoothly from one Position to the next and we want this to happen over a random amount of time (within a range that we will define). Armed with "seedRandom()" and "while", we're ready to tackle this thing. First, let's just go ahead and take a look at the finished code:

random motion with random durations


segMin = .3; //minimum segment duration
segMax = .7; //maximum segment duration
minVal = [0.1*thisComp.width, 0.1*thisComp.height];
maxVal = [0.9*thisComp.width, 0.9*thisComp.height];

end = 0;
j = 0;
while ( time >= end){
  j += 1;
  seedRandom(j,true);
  start = end;
  end += random(segMin,segMax);
}
endVal =  random(minVal,maxVal);
seedRandom(j-1,true);
dummy=random(); //this is a throw-away value
startVal =  random(minVal,maxVal);
ease(time,start,end,startVal,endVal)

Let's take a look at what has changed since the previous version of our random motion code. The main difference is the setup and execution of the "while" loop. There are several things going on here. The "while" loop is where the length of each "segment" of motion is determined. The segment length will be a random number between "segMin" and "segMax" (.3 and .7 seconds in this case). We are going to loop through the comp, starting at time 0, adding up the random segment lengths until we reach the current time. So, for our purposes here, it will not be necessary to revisit each frame every time the expressions runs - we only need to look at the start of each segment (which will be, on the average, about every .5 seconds or so). Meanwhile, we've also been incrementing "j" for each segment. That makes "j" unique for each segment and a perfect candidate for the seed for "seedRandom()". So when we drop out of the loop, we know the start and end times of the current segment ("start" and "end") and we have the seed ("j") that was used to generate the end time. Outside the loop (after the closing curly brace) we know that the random number generator is still seeded with the index ("j") for the current segment. So we call random() again to get the ending Position for this segment. We now have everything we need (segment start time ("start"), segment end time ("end"), and ending Position ("endVal")) except the starting Position. We can get that by backing up to the previous seed, re-seeding the random number generator, throwing away the first random number, and retrieving the starting Position ("startVal"). (See the sidebar below for a more detailed explanation of why we have to throw one random number away). Now we just apply the same ease() statement as before to generate our random-duration, smooth, random motion.


Everything from here on out gets much easier because it's all just variations on a theme. In the next section we'll extend these concepts to randomizing other properties.




previous next