Jan 12: State
No magic numbers
Last time we saw that Processing uses variables to keep track of some aspects of the state of the world (namely the mouse position). Two additional variables that it provides are width and height, which tell us how big the window is. They'll both be 100 unless we call size() in setup(), in which case they'll keep track of that number for us. Why does that matter, since we must know that we changed it? Well, if we just use the number 100 all over our sketch, it looks a little "magical" when we see it -- why was it we used 100 for that call? Then if we decide to make the window 400x400, we have to find all the 100s that had to do with the window size (but not other ones) and change them to 400. By using width and height, we don't have a mysterious number, and we don't have to remember to change it if we make the screen bigger.
As with the mouse position variables, these variables hold numbers, so we can treat them exactly the same. Thus we can add (+), subtract (-), multiply (*), and divide (/) them, among other things. Here's an example where we "mirror" the mouse position, by subtracting it from the width and/or height of the window:
void setup() { size(400,400); smooth(); noStroke(); frameRate(30); background(0); } void draw() { fill(0,10); rect(0,0,width,height); fill(255); ellipse(mouseX,mouseY,30,30); // Mirror it: ellipse(width-mouseX,mouseY,30,30); ellipse(mouseX,height-mouseY,30,30); ellipse(width-mouseX,height-mouseY,30,30); }
[applet]
Notice also that we do the fade-away with a rectangle of size widthxheight, so that if we set the window to be a larger size, the rectangle still covers it all.
Here's an example that sets the red component of the background color based on the x coordinate, and the blue component based on the y component. The formula calculates the position as a fraction of the width (or height), from 0 to 1, and scales that to be from 0 to 255. Also take a look at the Processing example Basics | Input | Mouse2D, which sets sizes according to mouse position.
void draw() { background(255*mouseX/width,0,255*mouseY/height); }
[applet]
It doesn't matter how big we set the window -- the sketch will still work correctly. (Try it.)
One more example: drawing lines from the corners to the mouse position.
void setup() { size(300,400); smooth(); background(0); } void draw() { background(0); // Guide stroke(255); line(0,0,mouseX,mouseY); line(0,height,mouseX,mouseY); line(width,0,mouseX,mouseY); line(width,height,mouseX,mouseY); // Mouse position fill(0,0,255); noStroke(); ellipse(mouseX,mouseY,15,15); }
[applet]
Our own variables
Suppose we want to draw a bunch of same-sized ellipses, one at (20,90), one at (65,30), one at (72,50), etc. To make sure they're all of the same size, we put the same values for their width and height, say 25,25. But what if we decide we want them all to be bigger? We're in the same situation as with the width and height of the window -- we've got magic numbers, and we need to remember which 25s we need to change. To help us out, Processing let us create our own variables to keep track of information. Let's start with the ellipse example.
float diameter = 25; // declare our variable and give it an initial value ellipse(20,90, diameter,diameter); ellipse(65,30, diameter,diameter); ellipse(72,50, diameter,diameter);
[applet]
All the syntax stuff for the example is discussed down in the Programming notes section. What we've done is declared a variable called "diameter" that holds a float (a number that could have a decimal part, though we don't give one here), and set it to be 25. We've then used that variable exactly like a number (as we have done with mouseX and so forth) to specify the ellipse's width and height. If we ever decide we want bigger ellipses, we just change the 25 up where "diameter" is declared. (Try it.)
As the name suggests, a variable can actually vary. For example, we could set the value of "diameter" to a different random number each frame; then all ellipses change diameter in synch.
float diameter = 25; // declare our variable and give it an initial value void setup() { smooth(); background(0); frameRate(5); } void draw() { background(0); diameter = random(25,75); ellipse(20,90, diameter,diameter); ellipse(65,30, diameter,diameter); ellipse(72,50, diameter,diameter); }
[applet]
The assignment diameter = random(25,75); gives a new value to the diameter variable. Note that when we update the value, we don't specify the type again; if we did, it would actually be seen as the declaration of a new variable of the same name.
We can use this same idea to create motion.
// The current coordinates of the ball, starting at the center float x=50, y=50; void setup() { smooth(); noStroke(); background(0); } void draw() { fill(0,3); rect(0,0,width,height); fill(255); ellipse(x,y,5,5); // Move the position by random steps in x and y x = x+random(-2,2); y = y+random(-2,2); }
[applet]
Here we declare variables named "x" and "y", with initial values both 50. The assignment then updates the values of the variables, setting x to its old value plus some random amount, and similarly with y.
Shiffman 4-3, 4-4, and 4-8 provide some more examples.
Events
The draw() function is called every frame. Similarly, we can define own functions that are called every time the mouse or a key is pressed. These are called events. For example, we can draw an ellipse only on the press of the mouse button, using the mousePressed() function.
void setup() { smooth(); noStroke(); background(0); frameRate(15); } void draw() { fill(0,10); rect(0,0,width,height); } void mousePressed() { fill(255); ellipse(mouseX,mouseY,25,25); }
[applet]
The keyPressed() function works similarly. E.g., I use it to capture the screenshot image for each sketch:
void keyPressed() { save("sketch.png"); }
The save() function can output different formats, depending on the file extension (here, png).
By using our own variables, we can make the mouse control aspects of the sketch. For example, we can make the random wanderer teleport to the current mouse location and continue wandering from there. Just add the following to the above sketch.
void mousePressed() { x = mouseX; y = mouseY; }
Programming notes
- Math
- We can do calculator-like things with numbers, including add (+), subtract (-), multiply (*), and divide (/) them. Other mathematical operations include abs() (absolute value) and min() (minimum). The results of these operations are themselves numbers. There are rules about the order in which the parts of complex expressions are evaluated (e.g., multiplication before addition). Parentheses can help make it clear.
- Declaration
- To create our own variable, we declare it by listing its type and its name, e.g., "float x;". In many cases, it is apropriate to give it an initial value, too, e.g., "float x=50;". Multiple variables of the same type can be declared together, separated by commas, e.g., "float x=50, y=50;".
- Local vs. global
- Where and how a variable can be used depends on where it is declared. A global variable is declared before the setup function. It can be used anywhere, and its value carries over from one function call to the next (e.g., from frame to frame). A local variable is declared inside a function definition (e.g., inside the draw function). It can only be used in that function, and its value is reset each time the function is called. So far we're only using global variables, but local variables are quite useful to hold temporary calculations, as we'll see in the next few lectures.
- Type
- A variable can only hold a certain type of value, which is specified as part of the declaration. The float type is for numbers that have a decimal part, while the int type is for those that don't. For example, ints are useful for counting, while floats are useful for coordinates. One key difference between the two is division -- division of ints throws away the fractional part; thus 1/2 == 0, whereas 1.0/2.0 == 0.5. Integers also support the "modulus" (remainder) operator, written with a percent sign; e.g., 4%2==0 while 5%2==1. There are some subtle issues in dealing with floats that hopefully we won't have to deal with here, but due to the finite precision in a computer, math sometimes does funny things. In both cases, numbers can't be arbitrarily large.
- Identifier
- Choose meaningful names for variables, so that when you read the code, you can tell what they stand for. There are restrictions on the names, though -- stick with alphanumeric (beginning with a letter), and avoid names already used by Processing (e.g., ellipse). They are case sensitive; unless you want your code to look like it's from the 70s, use mostly lower-case. A common practice for multi-word variables is to capitalize the first letter of the second (third, fourth, ...) word, e.g., myName.
- Assignment
- Assignment updates the value of a variable. It is written "variable = expression", where before the equals sign is the name of the variable, and after it is an expression of the appropriate type. We can combine math operators with assignment (e.g., +=, -=, *=), e.g., "variable += expression" is shorthand for "variable = variable + expression". Even shorter-hand expressions are available for incrementing by one, "x++", and decrementing by one, "x--".