Before we jump into functions, let’s quickly look at one more type of for loop– the DYNAMIC FOR loop.
So far we’ve just been using FOR loops based on a set of pre-determined conditions. But, we can make those conditions dynamic based on things like the mouse position, user input, or the amount of time that has passed in our program.
Here is an example that draws a series of lines that have spacing based on the position of mouseX:
int len = 200; int x = 60; int y = 100; int spacing = 10; void setup() { size(600, 600); } void draw() { background(255); spacing = int(map(mouseX, 0, width, 1, 100)); stroke(0); for (int i = 0; i< width; i+= spacing) { line (i, y, i, y+len); } }
A few things to note about the code above.
- This is the same code we looked at when we first learned about FOR loops. The only difference is that we mapped the spacing variable to our mouseX, but instead of going from 0 to width, we change the value range from 1 to 100. This way we never hit 0, and only going up to 100 makes more sense for the effect.
- Be very very very careful when using mouse coordinates in your FOR loop. At the very start of your program, mouseX will always be 0 until Processing catches up and checks where your mouse actually is. FOR loops generally don’t like 0’s. They don’t want i to be incremented by a 0 and you have to make sure that your exit condition will be true at some point to avoid infinite FOR loops.
- If you’re using mouseX, a good idea is to map mouseX to a variable that doesn’t ever hit 0. You can also use constrain() here to stop mouseX from hitting 0.
Functions
Why are functions useful?
First, they help reduce the amount of code that we have in our draw loop. As we start to write more complicated programs, our draw loop is going to grow longer and longer making our code hard to read and debug.
Functions are a means of taking the parts of our program and separating them out into modular pieces, making our code easier to read, debug, and revise.
Functions also allow us to reuse code without having to retype it. We can program a bouncing ball, but what if we want to have two bouncing balls? With functions, we can easily write the recipe for making a bouncing ball and then replicate that recipe as many times for as many balls as we want to have.
We’ve been using functions all along. Everytime we call rect(x,y,w,h); we’re calling a function. rect() just happens to be a function that Processing knows about. Now it’s time for us to start writing our own.
Creating a user-defined function in 3 easy parts
- A return type
- Function name
- Arguments
returnType functionName(arguments){
//code to run
}
Let’s start with return type. For now, your return type will be void. We’ve seen this before with void setup() and void draw(). Void just means return NOTHING. Next week we’re going to look at functions that return something to us, like a number (just like map(), constrain(), or random()). For now we’re going to make functions that are like line(), rect(), or ellipse() that don’t return anything to us, they just draw something to the screen.
Next is the name. This is an arbitrary name that we make up for our own purposes. This follows the same rule as naming variables- no spaces, the first letter should be lower case, and use camelCase when applicable.
displayBall() –> ok!
DisplayBall() –> not ok
display ball() –> not ok
Let’s make the above function that draws a ball:
void displayBall(){ fill(0); ellipse(100,100,50,50); }
Cool, now we’ve written our recipe for drawing a ball. But, unless we call displayBall() somewhere in our program (usually in our draw loop), we’re not going to see the ball. Think of a function just as a set of directions that we have lying around for whenever we want to call them up by calling our function.
void setup(){ size(600,600); } void draw(){ background(255); displayBall(); // calling the function! } void displayBall(){ fill(0); ellipse(100,100,50,50); }
Think back to our bouncing ball example:
// Declare global variables int x = 0; int speed = 1; void setup() { size(200,200); smooth(); } void draw() { background(255); // MOVE THE BALL x = x + speed; // BOUNCE THE BALL if ((x > width) || (x < 0)) { speed = speed * –1; } // DISPLAY THE BALL stroke(0); fill(175); ellipse(x,100,32,32); }
Now let’s rewrite this code into individual pieces- aka, moving the ball, bouncing the ball, and displaying the ball:
// Declare all global variables (stays the same) int x = 0; int speed = 1; // Setup does not change void setup() { size(200, 200); smooth(); } void draw() { background(255); move(); //call the move function bounce(); //all the bounce function display(); //call the display function } // A function to move the ball void move() { // Change the x location by speed x = x + speed; } // A function to bounce the ball void bounce() { // If we’ve reached an edge, reverse speed if ((x > width) || (x < 0)) { speed = speed * -1; } } // A function to display the ball void display() { stroke(0); fill(175); ellipse(x, 100, 32, 32); }
Check out how much more simple our draw loop is! We’re only using it to call functions. The complicated details of how to draw the shape and change its direction are reserved for modular functions that are well organized, easy to read, and can be turned off when we’re debugging.
A note on debugging -if your program isn’t behaving the way that you want it to you, try commenting out displayBall() to see if that’s your trouble, or comment out bounceBall(). You can turn off lots and lots of lines of code just by commenting out the line of code that calls the function.
The real power with functions is that we can call these functions over and over again, making lots and lots of balls! We won’t see that right now since all our balls will be drawn on top of each other in the same location, but next week we’re going to talk about arguments which help us personalize our functions so they can be a little bit different every time we call them. More to come!