Now you know how to create variables including arrays, and you know how to use for
loops to repeat some code for each element in an array.
You know that variables and values have a type, which tells the computer what kind of value it is.
This tutorial introduces a new kind of type: objects. Objects help you combine a group of related variables and functions into one unit, which helps you organize your code.
So far, you’ve been using primitive types like float
and boolean
. A primitive type holds a single, standalone value. For example:
float x = 7;
When you read this line of code, you know that x
holds a primitive value of 7
, but you don’t know whether there’s an associated y
value. It’s a single value, without any extra information.
Let’s start with some code that uses two primitive values to show a falling circle:
float x;
float y;
void setup(){
size(300, 300);
x = width / 2;
y = height / 2;
}
void draw(){
y++;
if(y > height){
y = 0;
}
background(50);
ellipse(x, y, 100, 100);
}
See the Pen by Happy Coding (@KevinWorkman) on CodePen.
This code uses primitive x
and y
variables to represent the position of a falling circle.
Next, let’s say you wanted to show multiple falling circles instead of just one. You might use one array to hold x values, and another array to hold y values:
float[] xArray;
float[] yArray;
float circleCount = 10;
void setup() {
size(300, 300);
xArray = new float[circleCount];
yArray = new float[circleCount];
for(int i = 0; i < circleCount; i++) {
xArray[i] = random(width);
yArray[i] = random(height);
}
}
void draw() {
background(50);
for(int i = 0; i < circleCount; i++) {
yArray[i]++;
if(yArray[i] > height) {
yArray[i] = 0;
}
ellipse(xArray[i], yArray[i], 25, 25);
}
}
See the Pen by Happy Coding (@KevinWorkman) on CodePen.
This code uses two arrays: one that holds the x values of the circles, and another that holds the y values. This approach of using multiple arrays to hold related data is called parallel arrays. This approach works, but it’s generally considered a bad idea because it can be difficult to understand code that uses a bunch of different arrays.
You know that a type tells the computer what kind of value a variable will hold. You’ve seen primitive types like float
and boolean
.
Primitive types hold a single value, and object types hold multiple related values. But how do you know what those related values are?
A class tells you about a particular object type. Similar to how you looked up primitive types in the reference, you can also find classes in the reference.
Specifically, let’s look at the PVector class.
A class is like a template that tells you what’s available inside an object. For example, the PVector reference tells you that the PVector
class contains x
, y
, and z
fields.
That means you can create a variable with an object type of PVector
, and that variable will contain x
, y
, and z
fields.
To create an object, use the new
keyword, followed by the name of the class, followed by parentheses ()
similar to a function call:
PVector myVector = new PVector();
The myVector
variable is of type PVector
, and this code uses the new
keyword along with the PVector
class name to create an object with x
, y
, and z
fields. This is called an instance of the PVector
class.
Now that you have this variable, you can use the dot operator to set the fields of that instance:
myVector.x = 150;
myVector.y = 200;
Now the myVector
variable holds a PVector
instance with an x
value of 150
and a y
value of 200
.
(You could also set the z
field, but the example only needs x
and y
so you can ignore z
for now.)
You can also use the dot operator to access those fields, and you can pass them into a function just like any other value:
ellipse(myVector.x, myVector.y, 100, 100);
Putting it all together, it looks like this:
size(300, 300);
PVector myVector = new PVector();
myVector.x = 150;
myVector.y = 200;
background(100);
ellipse(myVector.x, myVector.y, 100, 100);
See the Pen by Happy Coding (@KevinWorkman) on CodePen.
This code creates a myVector
variable with an object type of PVector
, and uses the new
keyword to create an instance of the PVector
class. It then sets the x
and y
fields of that instance, and then uses that instance to draw a circle.
Take a closer look at this line:
PVector myVector = new PVector();
The new
keyword tells the computer to create a new instance, and the PVector()
part tells the computer what class to create an instance of. The PVector()
part is also called a constructor because it constructs the instance.
The above constructor does not take any parameters, and the x
, y
, and z
fields inside the instance it creates will point to default values of 0
. This is also called a no-args constructor.
But like functions, constructors can also take arguments, passed in as comma-separated values inside the ()
parentheses. The constructor section of the PVector reference tells you what parameters the PVector
constructor can take.
For example, instead of setting the myVector.x
and myVector.y
values yourself, you can pass them into the PVector
constructor:
size(300, 300);
PVector myVector = new PVector(150, 200);
background(100);
ellipse(myVector.x, myVector.y, 100, 100);
And here’s the original example using a PVector
instance:
PVector circle;
void setup(){
size(300, 300);
circle = new PVector(width / 2, height / 2);
}
void draw(){
circle.y++;
if(circle.y > height){
circle.y = 0;
}
background(100);
ellipse(circle.x, circle.y, 100, 100);
}
See the Pen by Happy Coding (@KevinWorkman) on CodePen.
This code uses a PVector
instance to represent a falling circle.
One of the most important concepts to understand with objects is that each instance is independent of other instances of the same class. Changing one instance doesn’t change the other instances.
For example, this code creates two PVector
instances:
PVector redCircle;
PVector blueCircle;
void setup() {
size(300, 300);
redCircle = new PVector(100, 150);
blueCircle = new PVector(150, 100);
}
void draw() {
redCircle.x++;
if(redCircle.x > width) {
redCircle.x = 0;
}
blueCircle.y++;
if(blueCircle.y > height) {
blueCircle.y = 0;
}
background(100);
fill(255, 0, 0);
ellipse(redCircle.x, redCircle.y, 100, 100);
fill(0, 0, 255);
ellipse(blueCircle.x, blueCircle.y, 100, 100);
}
See the Pen by Happy Coding (@KevinWorkman) on CodePen.
This code creates two PVector
variables, each pointing to a different PVector
instance. Notice that the red circle and the blue circle are independent. Updating the red circle does not update the blue circle, and vice-versa.
This is a powerful idea, and it lets you organize your code as it gets more complicated. It’s also pretty confusing, and “thinking in objects” can take some time. Try changing the above code to add a green circle using a third PVector
instance.
Just like you can create an array of primitive values, you can also create an array of objects:
PVector[] circles = new PVector[10];
This line of code creates a circles
variable that points to an array that can hold 10 instances of the PVector
class.
for(int i = 0; i < circles.length; i++) {
circles[i] = new PVector(random(width), random(height));
}
This for
loop fills the array with PVector
instances. Each instance contains a random x
and y
value.
Then you can loop over the array to move and draw each circle:
for(int i = 0; i < circles.length; i++) {
circles[i].y++;
if(circles[i].y > height) {
circles[i].y = 0;
}
ellipse(circles[i].x, circles[i].y, 25, 25);
}
Putting it all together, it looks like this:
PVector[] circles = new PVector[10];
void setup() {
size(300, 300);
for(int i = 0; i < circles.length; i++) {
circles[i] = new PVector(random(width), random(height));
}
}
void draw() {
background(100);
for(int i = 0; i < circles.length; i++) {
circles[i].y++;
if(circles[i].y > height) {
circles[i].y = 0;
}
ellipse(circles[i].x, circles[i].y, 25, 25);
}
}
See the Pen by Happy Coding (@KevinWorkman) on CodePen.
Objects are a new way of organizing your code, but more importantly, they’re a new way of thinking about your code.
For example, when you think of a bunch of falling circles, you probably don’t think of them as a bunch of x values and a bunch of y values. You probably think of each circle as a cohesive unit, where each unit has an x and y value. Objects let you structure your code closer to how you structure the ideas in your brain.
This new way of thinking can be confusing. I remember being frustrated by it when I first started learning. But I also remember having an “ah-ha!” moment after working with objects for a while, where I finally understood them. I honestly believe that it has affected the way I’ve thought about not just code, but also the real world ever since.
So if all of this still feels confusing, that’s okay! It’ll become more natural as you write more code and see more examples that use objects.
This way of “thinking in objects” is called object oriented programming and it’s at the core of many languages and libraries.
Like I’ve said before, encountering errors and needing to debug problems is a normal part of writing code. And now that you’re using objects, you’re very likely to encounter a new type of error: the dreaded NullPointerException
.
For example, what do you expect happens when you run this code?
PVector myVector;
void setup() {
size(300, 300);
}
void draw() {
ellipse(myVector.x, myVector.y, 100, 100);
}
You might expect this sketch to draw a circle in the upper-left corner, with an x
and y
value of 0
. Instead, if you try to run this program, you’ll get this error:
NullPointerException
To understand this error, think about the default values of sketch-level variables. For primitive number types like float
, the default value is 0
, and for primitive boolean
values, the default value is false
. For object types, the default value is null
which means that it’s not pointing at any instance yet.
float myNumber;
boolean myBoolean;
PVector myVector;
void setup() {
size(300, 300);
}
void draw() {
background(50);
textSize(32);
text("myNumber: " + myNumber, 25, 75);
text("myBoolean: " + myBoolean, 25, 150);
text("myVector: " + myVector, 25, 225);
}
In other words, if you don’t specify a value, then you can pretend that you’ve specified the defaults:
float myNumber = 0;
boolean myBoolean = false;
PVector myVector = null;
The NullPointerException
error is caused by trying to use the dot operator on an object variable that points to null
.
That’s why this code generates a NullPointerException
:
PVector myVector;
void setup() {
size(300, 300);
}
void draw() {
ellipse(myVector.x, myVector.y, 100, 100);
}
This code tries to get the x
and y
values of myVector
which is set to the default value of null
. And since null
means that no object has been created, the computer doesn’t have an object to “ask” for its x
and y
values, so it generates the NullPointerException
instead.
To fix a NullPointerException
, you need to make sure any object variables you’re using are pointing to an instance:
PVector myVector;
void setup() {
size(300, 300);
myVector = new PVector(width / 2, height / 2);
}
void draw() {
ellipse(myVector.x, myVector.y, 100, 100);
}
Also note that the same thing is true of object arrays! For example:
PVector[] circles = new PVector[10];
This line of code creates an array that can hold 10 PVector
instances, but by default each index in the array points to null
(in other words, doesn’t point to anything). If you tried to use one of those elements:
ellipse(circles[0].x, circles[0].y, 100, 100);
You would get a NullPointerException
because the element is null
. That’s why you need to fill the array with instances first:
for(int i = 0; i < circles.length; i++) {
circles[i] = new PVector(random(width), random(height));
}
When you encounter a NullPointerException
, try using println()
statements to figure out which variable is null
, and then make sure that variable points to an instance instead of null
.
You can learn more about debugging here:
Now you’ve seen that classes contain fields. For example, the PVector
class contains x
, y
, and z
fields. Each instance of the PVector
class is an object that contains its own x
, y
, and z
fields, and each instance is independent of other instances.
In addition to containing fields, classes can also contain functions. Functions inside a class usually modify the state of an instance by changing the values of its fields, or they take some action based on the values of the fields.
As always, the Processing reference is your best friend. The PVector reference lists all of the functions you can call for instances of the PVector
class. For example, the PVector
class provides an add() function that adds values to the x
, y
, and z
fields of a specific instance.
To call an instance’s function, use the dot operator, then the name of the function, then any parameters the function requires in parentheses ()
.
PVector circle;
void setup(){
size(300, 300);
circle = new PVector(width / 2, height / 2);
}
void draw(){
circle.add(0, 1);
if(circle.y > height){
circle.y = 0;
}
background(100);
ellipse(circle.x, circle.y, 100, 100);
}
See the Pen by Happy Coding (@KevinWorkman) on CodePen.
Specifically, notice this line of code:
circle.add(0, 1);
This line of code calls the add()
function on the circle
variable, which points to an instance of the PVector
class. The 0
and 1
represent how much to add to the instance’s x
and y
fields respectively. After this line of code, the y
field inside the circle
instance will increase by 1
.
This might not seem very useful yet, but it comes in handy when you start using more complicated objects.
z
field of the PVector
class to hold a different size or speed for each falling circle.
Happy Coding is a community of folks just like you learning about coding.
Do you have a comment or question? Post it here!
Comments are powered by the Happy Coding forum. This page has a corresponding forum post, and replies to that post show up as comments here. Click the button above to go to the forum to post a comment!