I am a computer scientist; I approach the cosmos though a very different path: I take the simplest of principles - information theory, logic, design - and from them create new worlds that are bound only by my imagination.
— Grady Booch (@Grady_Booch) February 12, 2020
Let’s imagine a world in which a UFO, in the shape of a classic “flying saucer,” flies over a grassy field on a sunny blue sky day. Programming can enhance our imagination! We can program a device to draw and animate a spaceship. Let’s do this together. That’s right, it’s time for a Code-Along.
We’ll start in a separate browser window and navigate to the wonderful p5.js Web Editor. The editor provides us with some starter code in the JavaScript programming language together with the p5.js library. Hit the Play button to see what it does. You should see:
Okay! That’s a light gray canvas that is 400 pixels wide and 400 pixels high. Better than nothing, but we want a UFO flying over a grassy field on a sunny blue sky day! That’ll be on us. Baby steps first: we’ll begin by changing the background color.
Quick tip: Do your programming Live!
If it isn’t checked already, please check the “Auto-refresh” box above the code window. This way, your program runs as you type, which is generally super convenient.
Modify the code to read:
function setup() { createCanvas(400, 400) } function draw() { background("skyblue") }
If you had checked the auto-refresh box, your program updated and re-ran as you typed. If you didn’t, hit the Play button to see your change. (Then please check that box! You’ll be glad you did.)
setup and draw
The setup function holds code that runs once, at the beginning of the program. The draw function holds code that runs repeatedly, for each animation frame (something like 60 times per second by default). Of course, you don’t have to animate if you don’t want to: after all, a static scene is just an animation where all frames look exactly the same, right?
Now let’s draw a green grassy field (a rectangle) and a yellow sun (a circle). The canvas on which we draw has a coordinate system with (0, 0) at the upper-left corner with the x-coordinate increasing to the right, and the y-coordinate increasing downward:
According to the p5.js reference documentation, we draw rectangles by specifying the upper-left corner and the rectangle’s width and height; we draw circles by specifying the center point and the diameter. Colors are set with the fill
function. Let’s draw!
function setup() { createCanvas(400, 400) } function draw() { background("skyblue") fill("lightgreen") rect(0, 250, 400, 150) fill("yellow") circle(400, 0, 200) }
It looks like shapes are outlined with thin black strokes by default. We can git rid of the outlines by adding noStroke()
. We can add it to setup
, since it only has to run once.
noStroke()
line. How did it go?
Just like artists, craftspeople, and engineers, programmers design by tweaking their work until they get it just right (or close enough). Let's change our createCanvas
line to make our canvas wider and shorter:
function setup() { createCanvas(500, 300) noStroke() } function draw() { background("skyblue") fill("lightgreen") rect(0, 250, 400, 150) fill("yellow") circle(400, 0, 200) }
Uh-oh. That didn’t go so well. We had built our code assuming the canvas was always going to be 400 x 400 and figuring out a bunch of numbers on our own. Computers are good at math, so they should do the work, not us. Here’s what the designer in us should be thinking:
The programmer in us coverts these ideas to code in the most expressive way we can. You become skilled at implementing your ideas over time by studying prior art, by practicing, and through mentorship. In our code-along, we’re going to use the built-ins width
and height
, which are bound to the canvas width and height, allowing us to draw relative to the cavas size. In particular:
width
, 0)height*0.75
) and whose width is width
and whose height is height*0.25
.function setup() { createCanvas(500, 300) noStroke() } function draw() { background("skyblue") fill("lightgreen") rect(0, height*0.75, width, height*0.25) fill("yellow") circle(width, 0, 200) }
Now no matter the canvas size, the sun stays locked to the upper right corner and the grass takes up the lower quarter. If we want to change our canvas size, we just do it in one place and don’t worry about anything else.
Multiplication
Do you know what multiplication and division are for? Did anyone ever tell you? They are for scaling. Scaling is why we mulitply and divide.
Time to draw the UFO. A couple of ellipses ought to do it. The documentation tells us how to draw ellipses. We’re designing, so trying out different sizes and positions until we get things to look right is just fine. (See why having Auto-refresh checked is such a good idea?) After some experimentation, you’ll end up with something you can put at the end of your draw
function. Maybe something like this:
fill("gray") ellipse(50, 100, 80, 20) ellipse(50, 90, 40, 25)
Programming, like all forms of writing and performance art, is a way of expressing yourself. Whether you are writing essays, books, screenplays, short stories, résumés, technical documentation, magazine articles, manifestos, conference papers, or graphic novels, you want to communicate clearly to your readers. The same holds true for the programs you write.
What questions might a reader have looking at the code so far? They might ask “What do all those numbers mean?” “Why are there so many numbers?” And “What is all that stuff in the draw
function even doing?”
We can get clarity into our code by defining and using our own functions. You may have noticed functions are named blocks of code, like the two built-ins setup
and draw
we’ve seen earlier. Look how much readable our code is now:
function setup() { createCanvas(500, 300) noStroke() } function draw() { background("skyblue") drawGrass() drawSun() drawUfo() } function drawGrass() { fill("lightgreen") rect(0, height * 0.75, width, height * 0.25) } function drawSun() { fill("yellow") circle(width, 0, 200) } function drawUfo() { fill("gray") ellipse(50, 80, 80, 20) ellipse(50, 70, 40, 25) }
Now our code literally says: “To setup our app, create a 500 x 300 canvas and make sure we don’t outline any shapes. When drawing a scene, paint the canvas with the sky color, draw the grass, draw the sun, then draw the UFO.” Details of the way you draw the grass, sun, and UFO, have been factored out into their own functions, so readers looking at the draw
function see an abstract view of what it means to draw a scene. This a good thing: abstraction is one of the ways humans are able to deal with complexity.
We still have too many numbers in our UFO-drawing code. We can increase readability by using meaningful names; in programming, we speak of binding names to values. Let’s start by naming a couple things.
function drawUfo() { const ufoX = 50 const ufoY = 80 const ufoWidth = 80 fill("gray") ellipse(ufoX, ufoY, ufoWidth, 20) ellipse(ufoX, ufoY - 10, ufoWidth / 2, 25) }
These names are called variables if bound with let
, or constants if bound with const
. Variables can vary (have their values changed); constants are constantly bound to a single value. Constants are more common, and approprite here since we are just naming things. (Later, if we want to animate something, we can always switch its binding to let
.)
A bonus: Naming does much more than make code more readable! Another beautiful thing about using the names ufoX
and ufoY
is that if we changed our mind and wanted the ufo somewhere else, we don’t have to explicitly change the code for both ellipses, we only have to change the values bound to those names and voilà both ellipses will magically move together. We’ve tied the two ellipses into one. As a bonus, these name values make it easier to configure our UFO—its easier to change “settings” than to dig into drawing code to figure out how to reposition or resize things.
The first time our UFO is drawn, it appears at position (ufoX
, ufoY
). To animate the UFO, we need to change its position. Since the draw
function runs repeatedly, we only need to increase the value of ufoX
after we finish drawing a frame, so the next frame is drawn with the UFO at the new position, and so on!
We increase the (numeric) value of a variable by a certain amount using the +=
operator. To increase the value of the variable ufoX
by 1, we write:
ufoX += 1
But hang on, we can’t just drop that into our draw
function just now, for two reasons: (1) the variable ufoX
is buried inside the drawUfo
function, so draw
can’t see it! In programming-speak, the name is local to drawUfo
; we need it to be global in order to use it in both drawUfo
and draw
. (2) We made the variable const
, but since we want to change it, we need let
. These changes get it working:
let ufoX = 50 function setup() { createCanvas(500, 300) noStroke() } function draw() { background("skyblue") drawGrass() drawSun() drawUfo() ufoX += 1 } function drawGrass() { fill("lightgreen") rect(0, height * 0.75, width, height * 0.25) } function drawSun() { fill("yellow") circle(width, 0, 200) } function drawUfo() { const ufoY = 80 const ufoWidth = 80 fill("gray") ellipse(ufoX, ufoY, ufoWidth, 20) ellipse(ufoX, ufoY - 10, ufoWidth / 2, 25) }
We have animation! 🎉🎉🎉 But we can do better. Making it better is called refactoring. Several things can be improved:
ufoX += 1
looks a little misplaced inside draw
. The draw function reads “paint the background, draw the grass, draw the sun, draw the UFO, then do some really low-level looking math-y code.” (🎵One of these lines is not like the other. 🎵) That math-y code is supposed to move the UFO so we should write a separate function to move the UFO, and call it moveUfo
! Readability, right?ufoX
variable out to the global scope where it belongs, but now it doesn’t look as “connected” to the other ufo parameters. It is hard to “see” the UFO as a composite object. We should bundle the UFO’s x, y, and width properties together somehow. If they are related in real-life, they should be related (close together) in the code.So many issues? Don’t fret!
It is perfectly normal to write working code that is in need of improvement! Sometimes “getting it to work then making it better” is just how programming happens.
But more than this: Expect mistakes. Expect to write code that you did not know could be better. We are all learners. Expect yourself and others to find areas for improvement. Perfection in programming is elusive or impossible anyway. Always take advantage of opportunities to learn and improve.
We’re going to continue our code-along in class (sorry, web-only readers).
While you already know enough to make and invoke a moveUfo
function, the other tasks require an introduction to JavaScript’s objects for bundling UFO properties together, and the JavaScript if
-statement to get the UFO to fly back and forth.
Our completed code-along will look like this:
You can see the entire program here.
This is a good time to save your work. If you haven’t already, log in (you can first make yourself an account, or just log in via Google or GitHub). Select Save from the File menu. Optionally, you can also click the pencil icon next to the project name to rename your project.
Once you've logged in and saved your work, you will see a Share item on the File menu. The programming culture is generally one of sharing. Programmers are flattered when others use their code and extend it in new ways. Be a good citizen and give away your code for free. Select the Share menu item and grab the value in the “Present” box. Go to this URL in a different browser window, or even a different browser. Send it to a friend so they can admire your work. 🤩
If someone shares their code with you, and you decide to modify their code, that’s great too. Just remember to always give attribution to the original author.
Congratulations, you wrote, for your first program, a moderately interesting animation of a UFO, and along the way learned a lot of interesting things about programming. You now:
setup
and draw
functions from the p5.js librarywidth
and height
and why they are importantif
statement to implement conditional logic.You are now ready to strike out on your own! You are going to make an animation of a plane landing. You are going to be creative. With colors, scenery (forests? lakes? runways? air traffic control towers? and animated sunset, perhaps?), colorful aircraft, interesting landing approaches and effects, maybe even sound.
How will you do this?
You should be able to take all you learned about coordinates, colors, ellipses, rectangles, width
and height
, and variables and functions to build your plane lander. You will also need to browse the documentation to find new things, such as triangle
if you'd like to draw trees, and noLoop
which you will call to stop the animation once the plane has landed. Remember, though, that while it’s important to look things up in the documentation and search the web, please ask a neighbor, friend, TA, or instructor whenever it’s taking too long to find what you need.
Do you like to learn from instructional videos?
Consider watching Code! Programming with p5.js, an excellent series of videos by The Coding Train.
There are hundreds of different opinions on the right way to learn programming. Debates about which programming language you should learn first are widespread, but don’t pay too much attention to them. Learn several languages! JavaScript is one of those few languages every computer scientist should know, and there are excellent reasons for learning it first. That’s why we’re learning JavaScript today.
Programming languages are concerned with the basics of variables and functions and arithmetic and logic and making basic things happen. The powerful stuff, like graphics, sound, machine learning, and networking, comes in libraries written by others. We made our UFO program using Lauren McCarthy’s awesome p5.js library.
You can read about p5.js, its mission, its goals, and its philosophy, at its home page. You can also reach tutorials, examples, and reference material from the home page, too. p5.js wants to make programming accessible to everyone, especially those interested in the arts. (Perhaps you noticed in our code-along how you can be creative with programming—you don’t require Photoshop or Illustrator or Painting programs to express yourself!) Here's a fan article for the awesomeness that is p5.js. Read the article and follow the links!
p5.js is a fun way to get introduced to programming. It is one of many libraries you will use in your JavaScript programming career.
Now that you’ve started your programming journey, it’s time for some tips! How can you be a great programmer and have fun programming? Here are 10 suggestions, in no particular order.
${
and }
is called interpolating.
We’ve covered: