Programming with p5.js¶
p5.js is a JavaScript library based on the Processing programming language
p5.js provides many easy-to-use functions for doing things like:
- drawing 2-dimensional shapes, such as ellipses, rectangles, lines, polygons, curves, etc.
- display images and text in a way that lets them be modified in any way imaginable
- easily read input from the mouse and keyboard
- play sounds and music
- play and process video
we’ll use p5.js to create some nice pictures and interesting programs as a way to learn more about the features of JavaScript
Our First p5.js Program¶
to use p5.js you need a web page and some JavaScript code
our web basic p5.js web page will look like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>p5.js Starter Site</title>
<!-- load jQuery -->
<!-- <script src="scripts/jquery-3.2.1.min.js"></script> -->
<!-- load your JavaScript code -->
<script src="scripts/main.js"></script>
<!-- if you want p5.js, uncomment the following line -->
<script src="scripts/p5/p5.min.js"></script>
<!-- if you want p5.js sound functions, uncomment the following line -->
<!-- <script src="scripts/p5/p5.sound.minjs"></script> -->
<!-- load CSS styles from styles/mystyle.css -->
<link rel="stylesheet" type="text/css" href="styles/mystyle.css">
</head>
<body>
<h1>p5.js Starter Site</h1>
<div id="p5canvas"></div>
</body>
</html>
notice there is a div
element with the id
p5canvas
this is where the output of our p5.js code will be displayed
now in main.js
lets add this code:
'use strict';
function setup() {
// create the p5.js canvas and attach it to the HTML element whose id is
// p5canvas
createCanvas(500, 500).parent('p5canvas');
}
function draw() {
// draw an ellipse
// with center=(250, 250)
// width=200, height=80
ellipse(250, 250, 200, 80);
// draw a rectangle
// with upper left corner=(250, 250)
// width=200, height=80
rect(250, 250, 200, 80);
}
first, all p5.js that we write will have at least these two functions:
setup()
and draw()
the setup()
functions is always called first, and it is called exactly
one time; it’s purpose is to do whatever set-up is necessary for the
program, such as loading image files, setting initial values of variables,
etc.
after setup()
finishes, the draw()
function is called again and again
at approximately a rate of 60 times per second; the idea is that draw()
will draw one frame of animation on the canvas
the particular draw()
function above draws exactly the same thing every
time, and so the image never changes
mouseX and mouseY¶
here’s a p5.js program that lets you move an ellipse around the screen:
function setup() {
createCanvas(500, 500).parent('p5canvas');
}
function draw() {
ellipse(mouseX, mouseY, 200, 80);
}
mouseX
and mouseY
return the coordinates of the mouse pointer in the
p5canvas
if you move the mouse pointer around, then the values of mouseX
and
mouseY
usually change on each call to draw()
thus, the ellipse moves around the screen and leaves a trail
Clearing the Canvas¶
if you don’t want the ellipse to leave a trail as you move it around, then you
need to “clear the screen” at the start of each call to draw()
to do that, we’ll use the background
function to set the color of the
background:
function setup() {
createCanvas(500, 500).parent('p5canvas');
}
function draw() {
background(200, 200, 200);
ellipse(mouseX, mouseY, 200, 80);
}
background(200, 200, 200)
sets the entire canvas background to the RGB
color (200, 200, 200), i.e. a shade of gray
now the ellipse leaves no trail
The fill and stroke of a shape¶
the fill(r, g, b)
function sets the fill-color of the shapes that follow
it to be the RGB color (r, g, b)
the stroke(0, 0, 255)
function sets the color of the stroke, i.e. the
color of the line around the shape
function setup() {
createCanvas(500, 500).parent('p5canvas');
}
function draw() {
background(200, 200, 200);
fill(255, 0, 0);
stroke(0, 0, 255);
ellipse(mouseX, mouseY, 200, 80);
}
now the ellipse is red in the middle with a thin blue line (the stroke) around the edge
two other useful functions you should know about are noStroke()
and
noFill()
noStroke()
turns off the stroke completely
noFill()
turns off the fill completely, so that the whatever color is
under the ellipse will be seen
Other basic drawing functions¶
p5.js has a number of other functions for drawing shapes that you can find in the P5 reference
for example:
line(a, b, c, d)
draws a line between the points(a, b)
and(c, d)
triangle(a, b, c, d, e, f)
draws a triangle whose corner points are(a, b)
,(c, d)
, and(e, f)
quad(a, b, c, d, e, f, g, h)
draws a 4-sided quadrilateral whose corner points are(a, b)
,(c, d)
,(e, f)
, and(g, h)
- using
beginShape
,endShape
, andvertex
, you can create any sequence of connected points; see the reference for beginShape for examples of how this works arc(x, y, width, height, startAngle, stopAngle)
draws a portion of the ellipse whose center is(x, y)
and dimensions arewidth
andheight
. The ellipse is draw just from thestartAngle
to thestopAngle
- bezier and curve are a little more involved, but both can be used to draw different kinds of curves between points
another sometimes useful function is smooth()
, which, after it is called,
will try to draw curves in a way that makes their edges look smoother than
usual
Screen Coordinates¶
the p5.js canvas uses a standard 2-dimensional coordinate system for specifying the location of pixels
recall that the screen is essentially a big rectangle of pixels, where a pixel is the smallest element of the screen we can name
suppose we have a p5.js canvas that is 500 pixels wide and 500 pixels high
the columns of pixels are numbered, from left to right, 0, 1, 2, ..., 499 (not 500!)
the rows of pixels are numbered, from top to bottom, 0, 1, 2, ..., 499 (not 500!)
the coordinates of a pixel are specified by its column number x
followed
by its row number y
for example, the pixel (175, 388) is the pixel in column 175 and row 388
here are a few important pixels coordinates to know:
- (0, 0) is the upper-left-most pixel
- (499, 0) is the upper-right-most pixel
- (0, 499) is the lower-left-most pixel
- (499, 499) is the lower-right-most pixel
this coordinate system is very similar to the usual one from mathematics, except for a couple of important differences:
Example: Symmetry¶
the following examples shows how you can draw the same shape in pleasing symmetrical patterns
'use strict';
function setup() {
createCanvas(500, 500).parent('p5canvas');
smooth();
}
function draw() {
background(150); // try commenting out this statement
noStroke();
fill(255, 0, 0);
ellipse(mouseX, mouseY, 100, 100);
ellipse(500 - mouseX, mouseY, 100, 100);
ellipse(mouseX, 500 - mouseY, 100, 100);
ellipse(500 - mouseX, 500 - mouseY, 100, 100);
fill(0, 255, 0);
ellipse(mouseY, mouseX, 100, 100);
ellipse(500 - mouseY, mouseX, 100, 100);
ellipse(mouseY, 500 - mouseX, 100, 100);
ellipse(500 - mouseY, 500 - mouseX, 100, 100);
}
Random Numbers¶
JavaScript has a standard random number generating function called
Math.random()
that returns a random number in the range [0, 1)
p5.js provides a handy function called random(lo, hi)
that returns a
random number in the range [lo, hi)
if lo is 0, then p5.js lets you write just random(hi)
for example, here’s a program that changes the color of circle randomly on
each call to draw()
so that it appears to flash:
'use strict';
function setup() {
createCanvas(500, 500).parent('p5canvas');
}
function draw() {
background(150); // try commenting out this statement
noStroke();
fill(random(256), random(256), random(256));
ellipse(mouseX, mouseY, 100, 100);
}
another interesting trick is to add a small random number to, say, the position of the circle so that it appears to jiggle:
function draw() {
background(220);
noStroke();
fill(255, 0, 0);
ellipse(mouseX + random(-2, 2), mouseY + random(-2, 2), 100, 100);
}
Reading Mouse Clicks¶
let’s write a p5.js program that draws a circle wherever you click the mouse
to do this we’ll use the mousePressed()
function
mousePressed
is automatically called every time you press a mouse button
'use strict';
function setup() {
createCanvas(500, 500).parent('p5canvas');
background(200);
}
function draw() {
// do nothing
}
function mousePressed() {
fill(255, 0, 0);
noStroke();
ellipse(mouseX, mouseY, 100, 100);
}
draw()
does nothing in this program; the only time anything is drawn on
the screen is when a mouse button is pressed
Example: Simple Drawing¶
another useful feature of p5.js are the pmouseX
and the pmouseY
variables
pmouseX
is the x-location of the mouse pointer in the previous call to
draw()
pmouseY
is the y-location of the mouse pointer in the previous call to
draw()
here is a program that draws a line wherever you move the mouse:
'use strict';
function setup() {
createCanvas(500, 500).parent('p5canvas');
background(200);
}
function draw() {
// draw a blue line between the current mouse location
// and the mouse location in the previous call to draw
stroke(0, 0, 255);
line(mouseX, mouseY, pmouseX, pmouseY);
}
one way to make this program better is to draw only when a mouse button is pressed
we can do that using the pre-defined p5.js variable mouseIsPressed
and
if-statement like this:
'use strict';
function setup() {
createCanvas(500, 500).parent('p5canvas');
background(200);
}
function draw() {
stroke(0, 0, 255);
if (mouseIsPressed) {
line(mouseX, mouseY, pmouseX, pmouseY);
}
}
now the line is only draw when you hold down a mouse button, which is a big improvement
another nice little feature is to change the mouse when the a line is being drawn
you can do this with the p5.js cursor() function
function draw() {
stroke(0, 0, 255);
if (mouseIsPressed) {
cursor(ARROW);
line(mouseX, mouseY, pmouseX, pmouseY);
} else {
cursor(CROSS);
}
}
notice that we need the else
part of the if-statement to restore the mouse
pointer to the style we want it to be when a line is not being drawn
Reading the Keyboard¶
the p5.js pre-defined function keyPressed()
is called automatically
whenever you press a key on the keyboard
for example, this program draws a random circle whenever you press a key
'use strict';
function setup() {
createCanvas(500, 500).parent('p5canvas');
background(200);
}
function draw() {
// do nothing
}
function keyPressed() {
// choose a random fill color
fill(random(256), random(256), random(256));
noStroke();
let diam = random(10, 100);
let randX = random(500);
let randY = random(500);
ellipse(randX, randY, diam, diam);
}
after you press a few keys the screen will fill up with randomly colored circles
let’s add the ability to clear the screen when the user presses the C key
to do this, we will check the value of the pre-defined p5.js variable key
inside an if-statement; if key
is a 'c'
or 'C'
, then we’ll clear
the screen instead of drawing a circle
'use strict';
function setup() {
createCanvas(500, 500).parent('p5canvas');
background(200);
}
function draw() {
// do nothing
}
function keyPressed() {
if (key === 'c' || key === 'C') {
background(200);
} else {
// choose a random fill color
fill(random(256), random(256), random(256));
noStroke();
let diam = random(10, 100);
let randX = random(500);
let randY = random(500);
ellipse(randX, randY, diam, diam);
}
}
Example: A Bouncing Ball¶
let’s write a p5.js program that makes a ball bounce around the screen
this is quite a useful little program, as it shows some of the basic techniques for simple animation
we will represent our bouncing ball with four variables:
x
andy
represent the location of the center of the ball at any timedx
anddy
represent how far (and in what direction) the ball moves on each call todraw()
; (dx
,dy
) is called the velocity of the ball
the idea is that the ball will start at, say, the center of the screen, and
then on each call to draw()
we’ll draw the ball and then change it’s
x
-location by the amount dx
, and its y
-location by the amount
dy
this will cause the ball to move in a straight line at a constant speed
'use strict';
let x, y; // position of the ball
let dx, dy; // velocity of the ball
function setup() {
createCanvas(500, 500).parent('p5canvas');
// set the initial value of the ball's position and velocity
x = 250;
y = 250;
dx = -1.5; // try setting these
dy = 1.1; // randomly
}
function draw() {
background(220);
fill(255, 0, 0);
noStroke();
ellipse(x, y, 100, 100);
// update the position of the ball
x += dx;
y += dy;
}
this works, but there is an obvious problem: the ball quickly disappears off the screen and never returns
so lets modify it to “bounce” off the edges of the screen
the screen has four edges, so we will use four if-statements to check to see if the ball is off the screen
'use strict';
let x, y; // position of the ball
let dx, dy; // velocity of the ball
function setup() {
createCanvas(500, 500).parent('p5canvas');
// set the initial value of the ball's position and velocity
x = 250;
y = 250;
dx = -1.5; // try setting these
dy = 1.1; // randomly
}
function draw() {
background(220);
fill(255, 0, 0);
noStroke();
ellipse(x, y, 100, 100);
// update the position of the ball
x += dx;
y += dy;
// if the ball has gone off the screen, reverse its direction
// gone off left?
if (x < 0) {
dx = -dx;
}
// gone off right?
if (x >= 500) {
dx = -dx;
}
// gone off top?
if (y < 0) {
dy = -dy;
}
if (y >= 500) {
dy = -dy;
}
}
the statement dx = -dx;
causes the ball to reverse direction along the x-axis
the statement dy = -dy;
causes the ball to reverse direction along the y-axis
some other interesting things to try:
- instead of making the ball bounce on an edge, you can make it “wrap-around”
of the other side of the screen; for example, when the ball hits the right
edge, running
x = 0
insteaddx = -dx
will cause wrapping - you might prefer to make the ball bounce off an edge as soon as any part of hits the edge; in the above program the ball goes half-way into the edge, and doesn’t reverse until the center hits the edge; you can do this by checking the four points at the top, bottom, left, and right of the ball
- it’s easy to experiment with gravity-like effects by changing
dx
anddy
a little bit in each call todraw()
; for example, try puttingdy += 0.01;
right after the statementy += dy
— this will cause the ball to move differently, as if it is being pulled downwards by gravity - commenting-out the
background
statement will cause the ball to leave a trail, which can draw some interesting patterns
Global Variables and Local Variables¶
in the program above, we used four global variables: x
, y
, dx
,
and dy
a variable is said to be global if it was declared outside of any function
any function can read, or modify, a global variable
in contrast, a local variable is a variable declared inside some function
a local variable can only be read, or modified, inside the function where it was declared
p5.js uses, and encourages, the use of a lot of global variables
this is one of the reasons it is easy to use
however, most experienced programmers believe that global variables are generally a bad thing, and you should avoid them whenever possible
the problem with global variables is that it can be very hard to keep track of how they change within a large program since any part of the program could potentially modify it
it contrast, local variables can only be modified inside the function they were declared within, and so it is usually easier to understand how they are changing and being used
Hit Detection with Circles¶
hit detection is the name of the general problem of determining if a given point “hits”, or is inside of, some other shape
for example, how can you tell if the mouse pointer is inside, or outside, of a circle?
this turns out to be not too hard (at least compared to other kinds of hit detection!)
suppose the center of your circle is (x
, y
), and it has diameter
diam
then inside of draw()
you could use an if-statement like this to do
something when the most pointer is in the circle:
if (dist(x, y, mouseX, mouseY) < diam/2) {
// the mouse pointer is in the circle
// do something!
}
dist
is a pre-defined p5.js function that returns the distance between
two points, i.e. dist(x, y, a, b)
is the distance between points (x
,
y
) and (a
, b
)
it’s entirely up to you to decide what to when the mouse pointer is inside the circle
for example, you could make it stop by doing this:
if (dist(x, y, mouseX, mouseY) < diam/2) {
dx = 0;
dy = 0;
}
A Trick for Stopping Everything¶
the following trick is sometimes used in video games to stop all the animation
let gameOver = false; // global variable
// ...
function draw() {
if (gameOver) {
// do something, e.g. print "game over" on the canvas
return;
}
// ...
}
function keyPressed() {
gameOver = true;
}
in this example, when any key is pressed gameOver
is set to true
,
which causes the draw()
to call the code in the if-statement at the top
Using Text in p5.js¶
you display text in the p5.js canvas using the functions textFont
,
textSize
, and text
please see the p5.js documentation page for text to see an example of how to use fonts
also, the sample program at the end of these notes has more examples
Example: A Dodging Game¶
here’s a sample program that combines many of the small features discussed above
it’s a game where the goal is to avoid touching the red ball for as long as possible
you get 1 point every second that you survive; notice how the p5.js function
millis()
is used
millis()
returns the number of milliseconds (a millisecond is 1/1000th of
a second) that have elapsed since the start of the program
millis()/1000
returns the number of seconds since the start of the program
Math.floor(millis()/1000)
returns the number of seconds since the start of
the program, rounded-down to the nearest integer
'use strict';
// global variables
let x1, y1;
let dx1, dy1;
let diam1;
let fillColor1;
let x2, y2;
let dx2, dy2;
let diam2;
let fillColor2;
let gameOver;
function setup() {
createCanvas(500, 500).parent('p5canvas');
textFont('Georgia');
textSize(32);
let gameOver = false;
// initialize ball1
x1 = 250;
y1 = 250;
dx1 = random(-5, 5);
dy1 = random(-5, 5);
diam1 = random(50, 150);
fillColor1 = color(255, 0, 0);
// initialize ball 2
x2 = 250;
y2 = 250;
dx2 = random(-5, 5);
dy2 = random(-5, 5);
diam2 = random(50, 150);
fillColor2 = color(random(256), random(256), random(256));
}
function draw() {
if (gameOver === true) {
textSize(90);
text('Game over', 10, 250);
return;
}
background(220);
if (dist(x1, y1, mouseX, mouseY) < diam1 / 2) {
gameOver = true;
}
updateBall1();
updateBall2();
fill(0);
text('Score: ' + Math.floor(millis() / 1000), 20, 30);
}
function updateBall1() {
fill(fillColor1);
noStroke();
ellipse(x1, y1, diam1, diam1);
// update the position of the circle
x1 += dx1;
y1 += dy1;
let radius = diam1 / 2;
// off right?
if (x1 + radius >= 500) {
dx1 = -dx1;
}
// off bottom?
if (y1 + radius >= 500) {
dy1 = -dy1;
}
// off left?
if (x1 - radius < 0) {
dx1 = -dx1;
}
// off top?
if (y1 - radius < 0) {
dy1 = -dy1;
}
}
function updateBall2() {
fill(fillColor2);
noStroke();
ellipse(x2, y2, diam2, diam2);
// update the position of the circle
x2 += dx2;
y2 += dy2;
let radius = diam2 / 2;
// off right?
if (x2 + radius >= 500) {
dx2 = -dx2;
}
// off bottom?
if (y2 + radius >= 500) {
dy2 = -dy2;
}
// off left?
if (x2 - radius < 0) {
dx2 = -dx2;
}
// off top?
if (y2 - radius < 0) {
dy2 = -dy2;
}
}