What are Objects?
The ability to define functions as a way to organize and reuse code has been around since the very earliest programming languages, way back to the 1950s with languages like FORTRAN and COBOL. In the 1990s another paradigm (way of thinking) of programming gained popularity, it’s called Object-Oriented Programming (OOP for short). Most modern languages like p5.js, Python, Java, and C++ all support OOP.
To understand what OOP is all about, we first need to understand what an object is. In the real world, we are surrounded by objects every day, in fact, you are an object! Every object in our world has certain properties (that describe it) and certain behaviours (things it can do). For example, every human has properties such as height, weight, eye colour, etc.. and common behaviours like sleeping, eating, and coding.
In terms of programming, an object is a way of organizing code that combines related variables (properties) and functions (behaviours). Since just about anything in our world can be thought of in terms of its properties and behaviours this makes Object-Oriented Programming (OOP) a very natural way of simulating the real world in software.
A Quick Review
Let’s consider creating a bouncing ball simulation. In 2-3, we used variables and functions to do something like this. Below is the original code. You will see that our ball had three properties (variables) describing the state of the ball: ballX, ballY, and speedX and three behaviours (functions) related to the ball’s movement: display(), bounce(), and move():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
let ballX = 200 let ballY = 200 let speedX = 2 function setup() { createCanvas(400, 400) } function draw() { background(220) display() bounce() move() } // This function draws a yellow ball function display() { fill(255, 255, 0) ellipse(ballX, ballY, 40, 40) } // Thus function reverses the ball direction if we hit an edge function bounce() { if (ballX > width || ballX < 0) { speedX = speedX * -1 } } // This function changes the x location of the ball function move() { ballX = ballX + speedX } |
What is a Class?
Before we can create objects for use in our program, we need to create what is called a class. A good analogy for a class is a cookie-cutter. A cookie-cutter is not a cookie, but it describes what a cookie will be like and can be used to create as many cookies as we need. In programming, a class is code that describes what objects of that class (called instances) will be like.
Building a Class
As mentioned, before we can instantiate (i.e., create) objects in our code, we need to define a class; a template that describes the variables and functions that each object of that class (i.e., each instance) will have in common. Let’s build a class for our ball one step at a time.
Step 1:
First, we define a new class like this:
1 2 3 4 5 |
class Ball { // We’ll put more code here in Step 2 } |
It starts with the keyword class followed by the name of the class we are defining, then empty { curly braces } for the body of our class. In terms of naming a class, all of the usual naming rules for variables and functions apply, but you’ll notice one important exception: Class names always start with an Uppercase letter. This is a convention that all programmers follow so that it is obvious when reading code that something is a class rather than a variable.
Step 2:
Next, we need to add a special function called a constructor to the class. The job of the constructor is to define and initialize the properties (i.e., variables) that all objects of the class will have:
1 2 3 4 5 6 7 8 9 |
class Ball { constructor() { this.ballX = 200 this.ballY = 200 this.speedX = 2 } // We’ll put more code here in Step 3 } |
When we define functions inside a class we do not include the keyword function. The constructor() function header (line 2), must be typed exactly as shown. p5.js does not allow us to use a different name for this special-purpose function, although as we’ll see later it’s possible to add parameters.
On lines 3 to 5, I’ve simply moved all of the global variables we had that were related to our ball. The keyword this (followed by a dot .) is a required prefix for each variable assigned a starting value in the constructor() function. Any variable defined like this can be accessed by any function inside our class. In other words, these variables still have global scope. I’ll get more into “this.” (pun intended) next time and you’ll understand better why it’s needed.
Step 3:
Third, we need to add a function for each behaviour we want objects of this class to exhibit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class Ball { constructor() { this.ballX = 200 this.ballY = 200 this.speedX = 2 } display() { // Draw the ball fill(255, 255, 0) ellipse(this.ballX, this.ballY, 40, 40) } bounce() { // Reverse direction if we've hit a canvas edge if (this.ballX > width || this.ballX < 0) { this.speedX = this.speedX * -1 } } move() { // Change the x location of the ball this.ballX = this.ballX + this.speedX } } |
Lines 8 through 24, show the display(), bounce(), and move() functions. These are essentially the same as they were before except now they are defined inside our Ball class. Notice that they are all indented inside the { body } of the Ball class and that the keyword function is not used. Also, notice that every time we refer to one of the variables declared in the constructor, we must prefix its name with the keyword this followed by a dot (.).
That’s actually all there is to defining a simple class. However! At this point, we have not instantiated (created) any objects of the class that we can use in our code.
Step 4:
Next, we need to instantiate one (or more) objects of our class in a program:
1 2 3 4 5 6 |
let myBall function setup() { createCanvas(400, 400) myBall = new Ball() } |
On line 1 I declare a global variable that we’ll use to refer to a Ball object. Since I only want to create a Ball object once when the program starts, I instantiate a new Ball object inside setup(). The new keyword on line 5 automatically calls the constructor() function to initialize the variables for a new Ball object being instantiated. The assignment (=) operator sets the myBall variable to refer to the new Ball object in memory.
Step 5:
Finally, now that we have instantiated a Ball object and have a variable to refer to it, we can interact with the object by calling its functions, as shown here in the draw() function:
1 2 3 4 5 6 |
function draw() { background(220) myBall.display() myBall.bounce() myBall.move() } |
To call the functions within the object you must prefix the name of the function you are trying to call with the variable name we are using to refer to the object followed by a dot (.). This is called dot-notation in OOP.
The Complete Code
For reference, here’s the complete program with everything included together:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
let myBall function setup() { createCanvas(400, 400) myBall = new Ball() } function draw() { background(220) myBall.display() myBall.bounce() myBall.move() } class Ball { constructor() { this.ballX = 200 this.ballY = 200 this.speedX = 2 } display() { // Draw the ball fill(255, 255, 0) ellipse(this.ballX, this.ballY, 40, 40) } bounce() { // Reverse direction if we've hit a canvas edge if (this.ballX > width || this.ballX < 0) { this.speedX = this.speedX * -1 } } move() { // Change the x location of the ball this.ballX = this.ballX + this.speedX } } |
There is no syntax rule about the order of functions and classes in p5.js, but most programmers would expect to see setup() first, draw() second, followed by any additional functions, then any class code. I recommend you do the same!
Some Humble Advice
Whew! You may be feeling a little overwhelmed by all of the steps and new syntax details required to define a class, and instantiate an object. It’s worth spending some time comparing our new Object-Oriented version of the ball animation with our original version. If you really think about it, we’ve essentially only moved a few things around to format the code differently.
All of the OOP concepts described in this lesson translate into most other object-oriented, modern programming languages out there. It’s definitely worth spending time getting comfortable with this.
You Try!
- Customize the class code today such that the Ball moves in a random x and y direction (use random values between -5 and 5). We did an exercise like this in 1-11, but without OOP. Make sure that the direction that the Ball bounces makes sense in terms of physics. For example, if the Ball hits the left edge of the canvas, you should reverse the x-direction, but not the y-direction. Start the Ball at (200,100) on the canvas.
- Modify the bounce() function such that the colour of the Ball changes to a new random colour each time the Ball changes its x or y-direction. (Note: There’s a colour glitch in the animation below, ignore this.)