Now you know how to use classes like the predefined PVector
class. You know that classes let you group a set of fields together, like the x
and y
fields of the PVector
class. And you know how to create instances of a class using the new
keyword followed by the class name, also called a constructor.
Predefined classes like PVector
are useful, but you’ll likely encounter situations where you need a set of fields that are not captured by any existing classes. For example, PVector
gives you x
and y
fields, but what if you also wanted your object to contain r
, g
, and b
fields so each instance had its own color?
This tutorial teaches you how to define your own classes, which lets you group together any set of fields you can think of.
The first step in creating your own class is identifying what state you want to capture to describe objects. Let’s start with an example that doesn’t use any classes or objects:
float circleX = 150;
float circleY = 150;
float xSpeed = 1;
float ySpeed = 2;
void setup() {
size(300, 300);
}
void draw() {
background(50);
circleX += xSpeed;
if (circleX < 0 || circleX > width) {
xSpeed *= -1;
}
circleY += ySpeed;
if (circleY < 0 || circleY > height) {
ySpeed *= -1;
}
ellipse(circleX, circleY, 50, 50);
}
See the Pen by Happy Coding (@KevinWorkman) on CodePen.
This sketch uses four variables to store the state of a circle that bounces around the window.
So the state consists of four variables: circleX
, circleY
, xSpeed
, and ySpeed
.
Now, let’s say you wanted to show 10 circles bouncing around the screen. You could create four arrays: xArray
, yArray
, xSpeedArray
, and ySpeedArray
. But working with four different parallel arrays is confusing, so you might look for a class that contains those four variables. PVector
is pretty close, but it only contains x
, y
, and z
fields.
Instead of using a predefined class like PVector
, you can define your own class.
To define a class, use the class
keyword, then give your class a name. Then inside curly brackets {}
, write the body of the class. The body consists of the variables, constructors, and functions you want inside the class. Let’s start with the variables:
class Circle {
float x;
float y;
float xSpeed;
float ySpeed;
}
You can put this anywhere in your sketch. I usually put my class definitions at the bottom.
Now that you’ve defined a Circle
class, you can use it just like you used the PVector
class.
You can create a variable of type Circle
:
Circle circle;
You can use the new
keyword to create a new instance of Circle
:
circle = new Circle();
You can use the dot operator to set the fields inside the Circle
class:
circle.x = 150;
circle.y = 150;
circle.xSpeed = 1;
circle.ySpeed = 2;
You can also use the dot operator to access the fields inside the Circle
class:
circle.x += circle.xSpeed;
if (circle.x < 0 || circle.x > width) {
circle.xSpeed *= -1;
}
Putting it all together, it looks like this:
Circle circle;
void setup() {
size(300, 300);
circle = new Circle();
circle.x = 150;
circle.y = 150;
circle.xSpeed = 1;
circle.ySpeed = 2;
}
void draw() {
background(50);
circle.x += circle.xSpeed;
if (circle.x < 0 || circle.x > width) {
circle.xSpeed *= -1;
}
circle.y += circle.ySpeed;
if (circle.y < 0 || circle.y > height) {
circle.ySpeed *= -1;
}
ellipse(circle.x, circle.y, 50, 50);
}
class Circle {
float x;
float y;
float xSpeed;
float ySpeed;
}
See the Pen by Happy Coding (@KevinWorkman) on CodePen.
This code defines a Circle
class that contains x
, y
, xSpeed
, and ySpeed
fields, and then creates an instance of that class to represent a circle bouncing around the screen.
Now you know how to define a class that contains a certain set of variables. You can use the no-args constructor and then the dot operator to set those variables:
circle = new Circle();
circle.x = 150;
circle.y = 150;
circle.xSpeed = 1;
circle.ySpeed = 2;
But you can shorten this code by defining a constructor that takes these values as parameters, so that you don’t have to set each one individually.
A constructor looks a lot like a function, except it doesn’t have a return type and it’s always the same name as the class. Most constructors take values as parameters and use those to set class-level variables to store the state of the object.
class Circle {
float x;
float y;
float xSpeed;
float ySpeed;
Circle(float x, float y, float xSpeed, float ySpeed) {
this.x = x;
this.y = y;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
}
}
Now that you’ve defined this constructor, you can shorten this code:
circle = new Circle();
circle.x = 150;
circle.y = 150;
circle.xSpeed = 1;
circle.ySpeed = 2;
To this:
circle = new Circle(150, 150, 1, 2);
Here’s the full example:
Circle circle;
void setup() {
size(300, 300);
circle = new Circle(150, 150, 1, 2);
}
void draw() {
background(50);
circle.x += circle.xSpeed;
if (circle.x < 0 || circle.x > width) {
circle.xSpeed *= -1;
}
circle.y += circle.ySpeed;
if (circle.y < 0 || circle.y > height) {
circle.ySpeed *= -1;
}
ellipse(circle.x, circle.y, 50, 50);
}
class Circle {
float x;
float y;
float xSpeed;
float ySpeed;
Circle(float x, float y, float xSpeed, float ySpeed) {
this.x = x;
this.y = y;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
}
}
this
KeywordYou might be wondering what’s going on with the code inside the Circle
constructor. What does this
mean?
The Circle
class defines four variables:
float x;
float y;
float xSpeed;
float ySpeed;
And the Circle
constructor takes four arguments:
Circle(float x, float y, float xSpeed, float ySpeed) {
Notice that the constructor arguments have the same names as the class variables. So when the code inside the constructor wants to reference a class variable, it needs to use the this
keyword to point to the class variables instead of the argument variables.
this.x = x;
This line of code tells the computer to set the class-level variable x
to the value of the parameter variable x
.
If this
is confusing, you can avoid this problem by giving your constructor parameters different names than the class variables. But this pattern of using the same name for class variables and constructor arguments is very common, so you’ll likely tencounter it in code written by other people.
So far you’ve learned how to define a class that includes a set of fields, and a constructor that sets those fields.
Remember that classes can also contain functions, like the add()
function of the PVector
class. These functions often do something based on the values of the instance’s fields.
Defining a class function is a lot like defining a sketch function like setup()
and draw()
.
Inside your class, give your function a return type (or if it doesn’t return anything, give it a void
return type), then give it a name, and then list any arguments in ()
parentheses. Then in {}
curly brackets, write the code that should run when that function is called.
Here’s an example:
class Circle {
float x;
float y;
float xSpeed;
float ySpeed;
Circle(float x, float y, float xSpeed, float ySpeed) {
this.x = x;
this.y = y;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
}
void move() {
x += xSpeed;
if (x < 0 || x > width) {
xSpeed *= -1;
}
y += ySpeed;
if (y < 0 || y > height) {
ySpeed *= -1;
}
}
void display(){
ellipse(x, y, 50, 50);
}
}
The move()
function uses the class variables to handle moving and bouncing the circle, and the display()
function draws the circle.
This means your sketch-level draw()
function can be simplified, since all of the logic has been moved inside the Circle
class:
void draw() {
background(50);
circle.move();
circle.display();
}
Now instead of containing a bunch of code that moves, bounces, and draws the circle, the draw()
function tells the circle to move and draw itself.
Putting it all together, it looks like this:
Circle circle;
void setup() {
size(300, 300);
circle = new Circle(150, 150, 1, 2);
}
void draw() {
background(50);
circle.move();
circle.display();
}
class Circle {
float x;
float y;
float xSpeed;
float ySpeed;
Circle(float x, float y, float xSpeed, float ySpeed) {
this.x = x;
this.y = y;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
}
void move() {
x += xSpeed;
if (x < 0 || x > width) {
xSpeed *= -1;
}
y += ySpeed;
if (y < 0 || y > height) {
ySpeed *= -1;
}
}
void display() {
ellipse(x, y, 50, 50);
}
}
See the Pen by Happy Coding (@KevinWorkman) on CodePen.
When you create a class, you should try to include everything about an object inside that class. In other words, a class should be self-contained, so that the code that uses that class doesn’t need to worry about the details.
For example, the Circle
class includes everything needed to move, bounce, and display a circle, which means the draw()
function can tell the circle to do its thing instead of containing a bunch of circle-bouncing logic.
This process of including everything about an object inside a class is called encapsulation. The Circle
class encapsulates the concept of a bouncing circle, so the draw()
function can tell the circle to move, bounce, and draw itself, instead of doing all of that work directly.
void draw() {
background(50);
circle.move();
circle.display();
}
This is a little subtle, but it lets you organize your code into logical pieces, which becomes very important as you work with more complex code. This also helps you work with other people: imagine you’re responsible for the draw()
function, and somebody else is responsible for creating a new Square
class that showed a bouncing square. What would you be responsible for? What would they be responsible for? This way of “thinking in objects” lets you draw clear lines between coders, which makes working on a team much more manageable.
Let’s get back to the goal of showing 10 circles bouncing around the screen.
Now that you’ve encapsulated the concept of a bouncing circle inside the Circle
class, you can create multiple instances of that class to add more circles to your sketch.
To do that, you can use an array that holds Circle
instances.
Circle[] circles = new Circle[10];
This line of code creates an empty array that can hold ten Circle
instances. This doesn’t create any Circle
instances yet! You still have to create the instances and add them to the array:
for (int i = 0; i < circles.length; i++) {
circles[i] = new Circle(random(width), random(height), random(-3, 3), random(-3, 3));
}
This for
loop creates ten instances of the Circle
class, each with a random x
, y
, xSpeed
, and ySpeed
value.
Then you can loop over the array to move and draw each circle:
for (int i = 0; i < circles.length; i++) {
circles[i].move();
circles[i].display();
}
Putting it all together, it looks like this:
Circle[] circles = new Circle[10];
void setup() {
size(300, 300);
for (int i = 0; i < circles.length; i++) {
circles[i] = new Circle(random(width), random(height), random(-3, 3), random(-3, 3));
}
}
void draw() {
background(50);
for (int i = 0; i < circles.length; i++) {
circles[i].move();
circles[i].display();
}
}
class Circle {
float x;
float y;
float xSpeed;
float ySpeed;
Circle(float x, float y, float xSpeed, float ySpeed) {
this.x = x;
this.y = y;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
}
void move() {
x += xSpeed;
if (x < 0 || x > width) {
xSpeed *= -1;
}
y += ySpeed;
if (y < 0 || y > height) {
ySpeed *= -1;
}
}
void display() {
ellipse(x, y, 50, 50);
}
}
See the Pen by Happy Coding (@KevinWorkman) on CodePen.
Now if you want to add more circles, you only have to change the size of the array. And if you want to modify the logic of every circle (to wrap to the left side of the screen when it goes off the right side, or to change color, or to change size), then you only have to change the code in one place: inside the Circle
class. That’s encapsulation!
The important thing to understand about objects is that each instance is a self-contained unit. Changing one instance, either by modifying its fields directly or by calling its move()
function , does not change the other instances. That’s why you can have many circle instances bouncing around the screen, all with their own internal values for their x
, y
, xSpeed
, and ySpeed
fields.
So far, the Circle
class has contained four primitive fields representing its state: x
, y
, xSpeed
, and ySpeed
. But nothing prevents you from using other objects inside your class!
For example, here’s the Circle
class rewritten to use PVector
instances:
class Circle {
PVector position;
PVector speed;
Circle(float x, float y, float xSpeed, float ySpeed) {
position = new PVector(x, y);
speed = new PVector(xSpeed, ySpeed);
}
void move() {
position.add(speed);
if (position.x < 0 || position.x > width) {
speed.x *= -1;
}
if (position.y < 0 || position.y > height) {
speed.y *= -1;
}
}
void display() {
ellipse(position.x, position.y, 50, 50);
}
}
This Circle
class uses PVector
instances to represent its position and its speed. The rest of the sketch (the setup()
and draw()
functions) can stay the same, because the Circle
class encapsulates its own internal representation of a bouncing circle.
Like I mentioned in the using objects tutorial, objects aren’t just a new way of organizing your code, they’re a new way of thinking about your code.
Like many things in coding, there isn’t a single “correct” way to approach object oriented programming. From here on out, a lot of code becomes more subjective than you might expect. It’s less about what’s correct, and more about what makes the most sense to you.
Does it make more sense to represent a bouncing circle as four primitive variables, or does it make more sense to use two PVector
instances? Does that change if you were to encapsulate the concept of a color for each circle? What about a size?
There isn’t a right or wrong answer to any of those questions. That can be frustrating and confusing, but I also find it beautiful in its own way. The code you write is an extension of how your brain organizes the world.
Just don’t forget the this
keyword.
Flower
objects that grow over time.
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!