Changes over time
In this tutorial, I’ll show you how to make a simple p5.js sketch that changes as time goes on. Before we talk about that, though, I need to explain a little bit about how time works in Processing.
Setup and draw
In all the sketches I’ve shown you so far, there were two weird sections of the
program: the stuff in function setup() { ... }
and the stuff in function
draw() { ... }
. I didn’t tell you yet exactly what those parts of the code do,
and it’s going to be a while before I’ve taught you enough that you can
actually fully understand what those parts of the code “mean” and why they look
the way they do.
But to get us past the next hurdle, here’s the short way of explaining it. Any
statements you put in setup()
are run once, at the beginning of the program.
Any statements you put in draw()
are run over and over again, typically
many times every second.
“Wait, huh?” you say. “What do you mean? My sketches don’t look like they’re getting drawing over and over again to me. It just looks like it’s exactly the same static image.” And yes, you’re right! All of the sketches I’ve shown you so far do exactly the same thing each time they’re run, so there’s no appearance of change when you run the sketch.
But rest assured, the code in draw()
is getting run over and over again. You
can test this by running the following code:
function setup() {
createCanvas(400, 400);
}
function draw() {
console.log("mark");
}
Keep an eye on the debug area (beneath where you type the code). You’ll see the word “mark” appear many times very quickly.
What’s happening here? The console.log()
function, as a reminder, is a
function whose only purpose is to print a particular value to the debug area. Everything inside of draw()
, as mentioned above, gets executed many times per second. So the end result of the code above is massive repetition of the same console.log()
function call, over and over again.
Frames
The name in computer animation for a single call of the function that draws
something to the screen is a “frame.” Processing provides two handy ways to
work with frames: the function frameRate()
and the built-in variable
frameCount
.
The frameRate()
function allows you to set how often the code in draw()
will run. This is called “setting the frame rate.” Try running this sketch:
function setup() {
createCanvas(400, 400);
frameRate(1);
}
function draw() {
console.log("mark");
}
The call to frameRate()
should be in the setup()
part of your code, not
draw()
, since you only need to set the frame rate once. The number you put
between the parentheses determines how many times per second the code in
draw()
gets run. You’ll see that the above sketch still prints the word
mark
but does so much more slowly.
The frameCount
variable is built into Processing. You don’t have to define it
yourself—Processing defines it for you, before your sketch begins. The
frameCount
variable is special in that it changes on every frame: the first
time the code in draw()
is executed, frameCount
is equal to zero; the
second time the code in draw()
is executed, frameCount
is equal to one;
etc. This sketch is exactly like the one above, but instead of printing the
word “mark,” it prints the current value of the frameCount
variable:
function setup() {
createCanvas(400, 400);
frameRate(1);
}
function draw() {
console.log(frameCount);
}
If you run this sketch in the p5 web editor, you’ll see the sketch slowly counting up, one number per second, outputting the numbers in the console.
(Of course, you can change the number in frameRate()
to make the numbers go
faster.)
Animation with frameCount
The frameCount
variable is just like any other variable, in that you can use
it in expressions and function calls. Because frameCount
changes on every
frame, you can exploit its value to create simple animation. Here’s an example:
function setup() { createCanvas(400, 400); } function draw() { background(50); noFill(); stroke(255); strokeWeight(8); ellipse(frameCount, 200, 100, 50+(frameCount/2)); }
You should see a small ellipse that starts at the left-hand side of the screen and moves slowly to the right, gradually getting taller.
EXERCISE: Use the
frameCount
variable in other function calls. Use theframeCount
variable as part of expressions. See what happens!
Retaining the content of the previous frame
Here’s a version of the above sketch with one important difference. See if you can pick it out and reason about what’s happening.
function setup() { createCanvas(400, 400); background(50); } function draw() { noFill(); stroke(255); strokeWeight(8); ellipse(frameCount, 200, 100, 50+(frameCount/2)); }
That’s super weird! It looks like each frame is being drawn over the previous. What gives?
The first thing to know is that Processing doesn’t automatically clear the canvas on each frame. By default, whatever was on the canvas at the end of the previous frame remains on the canvas in the next frame.
So the difference between the two sketches lies in where we called the
background()
function. In the example above, the call to background()
happens in the setup()
code, which means that it only happens once. In the
draw()
code, there is no call to background()
, so the background doesn’t
get “reset” to a flat color on each frame.
If you don’t redraw the background in each frame, the effect is to make it seem
like the sketch is being drawn on top of itself, over and over again. If you
do call background()
in each frame, the effect looks more like animation.
You can use both methodologies to great effect!
Simple oscillation
It’s pretty cool to be able to make animations, but there’s a problem with
frameCount
: it grows linearly, so there’s no clear way to make an animation
that appears to loop: you can only make something that grows bigger or moves in
one direction. In this section, I’m going to show you a few simple strategies
for oscillation, or making something that goes back and forth between
different states, using only the linear frameCount
value as the source of
change.
Modulo
In mathematics, there’s an operation called “modulo.” This operation gives you the remainder of the result of dividing one integer by another. For example, the result of 11 modulo 5 is the remainder of dividing eleven by five, or one. (Five times two is ten, and you need one more to make eleven.)
In Javascript, there’s an operator for modulo that works just like the other
arithmetic operators in its syntax: %
. For example, put this statement into a
p5.js editor window and see what happens:
console.log(11 % 5)
You should see 1
in the output window. Try different values and see what
happens!
What’s interesting for our purposes is that we can use the modulo operator to create a very simple repeating counter. Our ability to do this comes from the fact that the modulo operation produces the following pattern:
0 % 5 => 0 (zero divided by five is zero, with a remainder of zero)
1 % 5 => 1 (one divided by five is zero, with a remainder of one)
2 % 5 => 2
3 % 5 => 3
4 % 5 => 4
5 % 5 => 0 (five divided by five is one, with a remainder of zero)
6 % 5 => 1 (six divided by five is one, with a remainder of one)
7 % 5 => 2 (etc.)
...
In other words, if you have a constantly increasing value on the left side of the modulo operator, and a constant value on the right side, the result of evaluating the modulo expression creates a series of repeating values from zero up to the value on the right side of the operator.
We can use this property of the modulo operator to create “loops” that repeat every n frames, where n is a number we can choose. For example, here’s a sketch that creates an ellipse that grows and resets to its original size every thirty frames:
function setup() { createCanvas(400, 400); } function draw() { background(50); noFill(); stroke(255); strokeWeight(8); ellipse(200, 200, (frameCount % 30)*10, 50); }
And here’s a sketch that draws a circle moving from left to right, which appears to “reset” to the left side of the screen when it reaches the right side. Additionally, there’s a second oscillation that effects the stroke weight:
function setup() { createCanvas(400, 400); } function draw() { background(50); noFill(); stroke(255); strokeWeight(4 + (frameCount % 16)); ellipse(frameCount % 400, 200, 100, 100); }
EXERCISE: Create a sketch with multiple oscillations of different periods using a modulo of the
frameCount
.
Sine
The modulo technique is great, but it can only easily produce loops that grow
linearly and loop abruptly. An easy technique for making animations that appear
to grow and recede smoothly is to calculate the
sine of the frameCount
variable.
Right now, I’m not going to go into detail about what the “sine” function is or
how it works. For our purposes, the main purpose of calculating a value’s sine
is to use that value to create oscillations. The sine function is available in
p5.js using the sin()
function. Unlike other functions we’ve discussed so
far, the purpose of sin()
isn’t to draw something to the screen, but to take
a single value and return another value after applying a mathematical operation
to it.
The sin()
function takes a single parameter, and evaluates to a number from
-1 to 1. Evaluating the sin()
function with various values looks like this:
sin(0) = 0
sin(0.39) = 0.38
sin(0.78) = 0.70
sin(1.17) = 0.92
sin(1.57) = 1
sin(1.96) = 0.92
sin(2.35) = 0.70
sin(2.74) = 0.38
sin(3.14) = 0
sin(3.53) = -0.38
sin(3.92) = -0.70
sin(4.31) = -0.92
sin(4.71) = -1
sin(5.10) = -0.92
sin(5.49) = -0.70
sin(5.89) = -0.38
sin(6.28) = 0
In other words, the sine of zero is zero; the sine of pi/2 (~1.57) is one; the
sine of pi is zero; the sine of 3/2pi (~4.71) is negative one; and the sign of
2*pi (~6.28) is 0 again. Increasing values passed to sin()
will still result
in values between -1 and 1.
So what if we pass the frameCount
variable to sin()
? Well, you’d get
something like this:
function setup() { createCanvas(400, 400); } function draw() { background(50); noFill(); stroke(255); strokeWeight(8); ellipse(200, 200, 100+sin(frameCount), 100); }
… which isn’t very impressive. The circle is just… jiggling a little.
That’s because the sin()
function always returns a value from -1 to 1. If
we want this change to be really visible, we need to multiply the result so
that the value will make a difference of more than one pixel either way:
function setup() { createCanvas(400, 400); } function draw() { background(50); noFill(); stroke(255); strokeWeight(8); ellipse(200, 200, 100+(sin(frameCount)*50), 100); }
Okay, that’s better! But it’s a bit, uh, frenetic. That’s because the sin()
function produces its entire cycle of values counting up to around six, and
frameCount
moves counts past six very quickly (at the default frame rate, ten
times a second!). To make the oscillation slower, we need to divide the
frameCount
value by some other value. Let’s make it ten times slower by
dividing by ten:
function setup() { createCanvas(400, 400); } function draw() { background(50); noFill(); stroke(255); strokeWeight(8); ellipse(200,200,100+(sin(frameCount/10)*50),100); }
Most of the time you use the sin()
function, you should use it in combination
with two other values, which determine the amplitude of the oscillation
(i.e., how big the numbers get) and the frequency of the oscillation (i.e.,
how fast it goes). It’ll look something like this:
x + (sin(frameCount / y) * z)
… where x
, y
and z
are all numbers. Making y
bigger will make the
oscillation slower; making z
bigger will make the oscillation larger.
The x
value is the oscillation’s center point, i.e., what value is the
“resting place” of the oscillation.
Repetition and variation
Here’s a sketch that makes a series of circles that move across the screen,
using a sin()
to control their horizontal positions:
function setup() { createCanvas(400, 400); } function draw() { background(50); noStroke(); fill(255); for (let i = 0; i < 5; i++) { ellipse(200 + sin(frameCount/10) * 20, 100+(i*50), 40, 40); } }
And here’s a slightly more sophisticated example, which uses the loop variable
i
to introduce subtle variations to each circle:
function setup() { createCanvas(400, 400); } function draw() { background(50); noStroke(); for (let i = 0; i < 5; i++) { fill(frameCount % 255); ellipse( 200+(sin(frameCount/(i+10))*(i+20)), 100+(i*50), 40, 40); } }
The tricky part of this example is the following expression:
200+(sin(frameCount/(i+10))*(i+20))
To better understand this expression, try translating it into English, then try
calculating what its value would be for certain values of frameCount
and i
.
Changing variables over time
In previous examples, the variables we’ve used only ever have one value: the
value that we set for them initially in the part of the code before setup()
.
But the value of variables can change over the course of the program: once
we’ve set a variable, we can direct Javascript to overwrite the current value
of that variable with something else. You can use this property of Javascript
to write sketches that change over time in a slightly more sophisticated
manner than is possible using just the frameCount
variable.
As you’ll recall, the syntax for declaring a variable looks like this:
let x = expr;
… where x
is the name of the variable, and expr
is some expression whose
value should be assigned to x
. You can change the value of a variable using
the following syntax:
x = expr;
… where, again, x
is the name of a variable, and expr
is an expression
whose result will be assigned to x
.
Here’s a sketch that declares two variables, foo
and bar
, and then makes
changes to those variables inside the draw()
code.
let foo = 0; let bar = 400; function setup() { createCanvas(400, 400); } function draw() { background(50); noFill(); stroke(255); strokeWeight(8); ellipse(foo, 150, 45, 45); ellipse(bar, 250, 45, 45); foo = foo + 1; bar = bar - 1; }
As you can see, on each frame, the code in this sketch adds one to foo
and
subtracts one from bar
.
Writing an expression to simply take the current value of a variable, and then assign back to it that value plus or minus one is so common that there’s a shortcut for it. This expression:
foo = foo + 1;
can be rewritten as:
foo += 1;
You can use values other than 1
in this expression as well.
Here’s an example that foregoes a lot of the weird arithmetic we had to do in
the above examples with the sin()
function by incrementing a value by a small
amount on each frame (instead of dividing frameCount
):
let xpos = 0; function setup() { createCanvas(400, 400); } function draw() { background(50); noFill(); stroke(255); strokeWeight(8); ellipse(200 + sin(xpos) * 100, 200, 45, 45); xpos += 0.05; }
Let’s add in a Y position just for fun:
let xpos = 0; let ypos = 0; function setup() { createCanvas(400, 400); } function draw() { background(50); noFill(); stroke(255); strokeWeight(8); ellipse( 200+sin(xpos)*100, 200+sin(ypos)*100, 45, 45); xpos += 0.05; ypos += 0.04; }
EXERCISE: What if the amount by which you adjusted
xpos
andypos
on each frame was itself changing over time, instead of a static value?
Random changes
In my opinion, computer-generated art only really starts to get fun once it has the appearance of not being deterministic. So let’s introduce some randomness into our sketches.
Processing has a function called random()
that evaluates to a random number
between zero and one. See the reference page for more information and
alternate ways to call the function.
The numbers produced by the random function will change every time you run the
sketch, and from one function call to the next.
You can use the random()
function to draw shapes at random locations in the
sketch:
function setup() { createCanvas(400, 400); background(50); } function draw() { noStroke(); fill(random() * 255); ellipse(random()*400, random()*400, 50, 50); }
You can call the random()
function with two parameters, in which case it will
return a random number between the values of the first and the second
parameter. To create a shape that appears to “wander” around the screen:
let xpos = 200; let ypos = 200; function setup() { createCanvas(400, 400); } function draw() { background(50); noFill(); stroke(255); strokeWeight(8); ellipse(xpos, ypos, 45, 45); xpos += random(-2, 2); ypos += random(-2, 2); }