Wednesday, August 7, 2019

p5 reference: the "good parts" edition

UPDATE: Ben Moren made a great cheatsheet for beginners [My Mirror Here] that is a good complement to what I have here...

I recently taught a 2 hour class in programming for fun - "After Hours: Making Virtual Toys, Games and Art".  It was a fun chance to show people (both experienced developers and programming novices) how to use Processing and P5.js - and the P5 editor is the best online thing I've seen for learning and sharing.

Both P5 and Processing have great websites with lots of learning tools, and it is awesome how all of their reference pages come with a tiny, ready-to-run example. But, for example, the p5 reference page has dozens and dozens of functions and special variables - I think that is intimidating to a new user who might only need to know 10-20 commands or so to make a lot of cool stuff. I made this page to be just those functions and special variables I use again and again in my decade and a half of Processing and P5.  (Stuff that's marked "BONUS" isn't as crucial but can add in the fun.)

Start up:
  • setup() - make this function and it runs once, at he beginning
  • createCanvas() - call this in setup() to set the width and height of your canvas
  • BONUS:  preload() - if you need to load images etc and don't want to start until its loaded, you should know about making this function...
Your main loop:
  • draw() - make this function and it gets called every clock tick
  • background() - many times your routine will start with this to erase the background (see the coloring things section below, and remember you can even set the transparency value for a cool ghost effect.)
  • width and height - often as you draw it's good to check these special variables and scale things properly
  • BONUS: call noLoop() if you don't actually need to loop - that can save device battery! And then call loop() if you change your mind
  • EXTRA BONUS: frameCount is a variable telling you the current frame number... sometimes good to setup a score time or somesuch if the goal is to stay alive as long as possible...
  • EXTRA EXTRA BONUS: saveCanvas() makes it super easy to save a snapshot of what's on your canvas... just be careful if you're doing it again and again and again inside your draw() loop!
Things to draw:

It's probably easy for non-programmers to forget how weird it is that for x,y coordinates, 0,0 is the top left corner, and 100,100 (or whatever the Canvas size is) is the bottom right... that applies to all of these commands:

Coloring things and pen widths:

The p5.js color system is a little odd but simple and fun once you get it... functions like background() and fill() and stroke() can take 1 argument (a number from 0 (black) to 255 (white)), 3 arguments (3 numbers from 0-255 for Red, Green, Blue), and then for either of those you can add an EXTRA argument to say how transparent it is (0 = it's invisible! 255 = it's fully opaque)
Interact-y stuff (mouse and keyboard):
BONUS Content:
function draw() {
  if(mouseIsPressed) line(pmouseX,pmouseY,mouseX,mouseY);
}
is like the world's simplest paint program!

Mathy Mathy Math
  • random() - often for generative art, it's nice to have a random number...
  • dist() - Pythagorean theorem - how far apart are two points? (useful to know if two circles overlap, say)
  • map() - if you want to take a number that might be in a range from 0 to 1 and change it to a range from 0 to 100, map() is your friend
  • cos() / sin() - sometimes you need to remember your old Geometry class...
  • BONUS: constrain() - limit a value to a range
External graphics

 Because I'm lazy, I often prefer to make games and toys with the shape drawing stuff, but sometimes it's good to know how to import an image...
  • loadImage() - load the image
  • image() - draw the image you loaded - you can mess with its size and scale and stuff too

BONUS: Changing the frame of reference for where we draw

It's often useful to change where "0,0" is, and/or rotate stuff for some cool effects...
  • push() - remember where we were drawing now
  • translate() - move to where we want our new center of drawing to be
  • rotate() - spin stuff around (PROTIP: you usually want to call translate() then rotate() unless you mean to spin the world
  • pop() - pop back to where we push()d before

BUT WAIT THERE'S MORE!

These are only the commands I've found myself using again, but P5 does a ton more.. I've barely touched the sound APIs (so many of my games are quieter than they should be) - there's also stuff with microphones, and some mobile-specific stuff as well.  The p5 examples page can be an inspiration in many ways, and teach you basic programming concepts too.

Nerdy Javascript basics...
This page isn't meant to be a programming class, and the p5 examples page can teach you many things, and there are some great youtube tutorials - The Coding Train seems pretty cool.

Obviously, you should know about variables - historically you declare a variable with var (like var x = 10; )though more modern javascript favors saying let x = 10; (for things that change) or const x = 10; for things that are "constant" and don't change.

Also, it's usually pretty easy to set up an array (using [] symbols) of objects (key-value pairs that are wrapped in {}). So you might have an array of, say, circle objects that you "iterate" over (i.e. go over each one) and moving them (maybe drawing them to the mouse?) and drawing them, so they can be seen

One thing that's trickier, oddly, is safely removing stuff from an array. In some cases, you might just want to mark something as "removed" and ignore it... but I'm going to include an example of using the (modern javascript, not p5 in particular) filter command to remove it.

Here's a simple program that just puts 10 dots on the canvas:
var dots = [];
function setup() {
  createCanvas(400, 400);
  for(var i = 0; i < 10; i++){
     dots.push({x:random(width),y:random(height)}); 
  }
}

function draw() {
  background(220);
//fancier way of the old for(var i = 0; i < dots.length; i++) 
// { var dot = dots[i];
  for(dot of dots) { 
     ellipse(dot.x,dot.y,20,20); 
  }
}

So each dot is an object with an x and a y propery: {x:_, y:_ }. Lets make a version of this program where if you click on a dot, it gets an extra "imdead" property, and then skip drawing it. Here's all the code with the new parts in bold...

var dots = [];

function setup() {
  createCanvas(400, 400);
  for (var i = 0; i < 10; i++) {
    dots.push({x: random(width),y: random(height)});
  }
}

function draw() {
  background(220);
  for (dot of dots) {
    if (!dot.imdead) {
      ellipse(dot.x, dot.y, 20, 20);
    }
  }
}

function mousePressed() {
  for (dot of dots) {
    if (dist(dot.x, dot.y, mouseX, mouseY) < 10) {
      dot.imdead = true;
    }
  }
}

So, if we didn't want to check imdead all the time (in this example it's no problem to ignore the dead ones, but either for performance reasons or just for simpifying code we might not want to have to keep checking) we can change that draw() loop to what follows:

function draw() {
  background(220);
  for (dot of dots) {
      ellipse(dot.x, dot.y, 20, 20);
  }
  dots = dots.filter(dot => (!dot.imdead));
}

we got rid of the imdead check wrapping the ellipse() command, and then used a fancy new js "filter()" that kicked out all the imdead dots. Obviously there are many ways we could have used that filter (like right in the mousePressed() for instance) but in my experience, setting a property like that and then removing the thing later is something that comes up a lot in games I make. (Like maybe the alien plummets to its death before being removed from the screen...)

No comments:

Post a Comment