Working with Objects using p5.js

Authored by John Kuiphoff


OK - we got the basics down now. We rule! It's time to start building more sophisticated applications that resemble the kinds of apps we use on a daily basis.

Up until now, almost everything that we've created used procedural programming. Procedural programming uses a list of instructions to tell the computer what to do. Procedural programming is a bit like following a cooking recipe. However, we will now embark down a new path that will make our programs more closely mimic what occurs in the real world. In this lesson, we will learn the basics of objected oriented programming. Object oriented programming allows us to make objects on the screen that are more like "things". Things that have names, properties and actions. Let's think about an object that exists in the real world.

One Car

How about a car? A car is an object that exists in the real world. Let me tell you about my own car.

My car is a Hyundai Accent. Let's name it "myCar".

myCar also has properties. Properties in object oriented programming are like "variables" in procedural programming. myCar is brown. It currently has 63,565 miles on it. And sadly, it doesn't have bluetooth.

myCar can also do a few fancy things. Methods in object oriented programming are like "functions" in procedural programming. It can drive forward. It can also stop.

In the example below, we will create a "Car" class. It's almost like building our own car factory.

function setup()
{
  createCanvas(800, 400);
}

function draw()
{

}

// car constructor
function Car()
{
  this.xpos = 0;
  this.ypos = random(height);
  this.speed = 2;
  this.c = color(153, 102, 51);

  // drive method
  this.drive = function()
  {
    if(this.xpos > width)
    {
      this.xpos = 0;
    }
    this.xpos = this.xpos + this.speed;        
  }

  // brake method
  this.brake = function()
  {
    if(this.speed > 0)
    {
      this.speed = this.speed - 0.3;
    } else {
      this.speed = 0;
    }        
  }

  // display method
  this.display = function()
  {
    // body of the car
    fill(this.c);
    rectMode(CORNER);
    rect(this.xpos, this.ypos, 100, 50);

    // wheels
    fill(0);
    ellipse(this.xpos + 20, this.ypos + 45, 40, 40);
    ellipse(this.xpos + 80, this.ypos + 45, 40, 40);
  }
}

The code above has the potential of creating car objects. It's like we built a car factory. However, we haven't actually created any car objects in the setup() or draw() functions yet. We will order new cars in the next step. But first, let's break down the code into more manageable chunks:

When we create a car, the code inside of the Car() function will execute. When working with objects, we call this function the "constructor" - the name of the constructor is normally capitalized. The Car() function in this case assigns four properties for the car: xpos, ypos, speed and c. Although I didn't add properties for miles or bluetooth in this example, I could have done so by declaring: myCar.miles = 63565; and myCar.bluetooth = false;

The drive method increases the "xpos" of the car.

The brake method decreases the "speed" of the car until it stops completely.

The display method draws the car onto the screen using it's "xpos" and "ypos" properties.

Also you might have noticed the weird "this" word. It basically connects properties (variables) and methods (functions) to an object. It is kinda weird. The P5js textbook says the following about "this": (so sorry, I couldn't help myself with that one) "JavaScript has a special keyword, this, that you can use within the constructor function to refer to the current object." - McCarthy, Lauren; Reas, Casey; Fry, Ben (2015-10-12). Getting Started with p5.js: Making Interactive Graphics in JavaScript and Processing (Make) (Kindle Locations 2858-2859). Maker Media, Inc. Kindle Edition.

Now that we have the ability to create a car. Let's actually create one:

var myCar;
			
function setup()
{
  createCanvas(800, 400);
  
  // create the car
  myCar = new Car();
}

function draw()
{
  // clear background
  background(255);
  
  // check the xpos of myCar
  // if myCar is approaching the edge of the screen, apply the brakes
  if(myCar.xpos > 600)
  {
    myCar.brake();
  } 
  
  // drive car
  myCar.drive();
  
  // display the car
  myCar.display();
  
  // display the speed
  fill(50);
  noStroke();
  text("Speed: " + myCar.speed, 25, 25);
  
  // display instructions
  text("Press any key to reset the xpos of myCar", 25, height - 50);
}

function keyPressed()
{
  myCar.xpos = 0;
  myCar.speed = 2;
}

// car constructor
function Car()
{
  this.xpos = 0;
  this.ypos = 200;
  this.speed = 2;
  this.c = color(153, 102, 51);

  // drive method
  this.drive = function()
  {
    if(this.xpos > width)
    {
      this.xpos = 0;
    }
    this.xpos = this.xpos + this.speed;        
  }

  // brake method
  this.brake = function()
  {
    if(this.speed > 0)
    {
      this.speed = this.speed - 0.05;
    } else {
      this.speed = 0;
    }        
  }

  // display method
  this.display = function()
  {
    // body of the car
    fill(this.c);
    rectMode(CORNER);
    rect(this.xpos, this.ypos, 100, 50);

    // wheels
    fill(0);
    ellipse(this.xpos + 20, this.ypos + 45, 40, 40);
    ellipse(this.xpos + 80, this.ypos + 45, 40, 40);
  }
}

Wow, we're starting to accumulate a lot of code. So let's talk about what we've done so far. First, we declared a global variable: var myCar; at the top of the script (so that all of our functions have access to it). Then, we actually created a new car object in the setup function: myCar = new Car();. When we create a new car, the constructor automatically runs. In the draw() function, we tell the car to drive, but to apply the brakes if we are approaching the edge of the screen. It's important to note that we have access to the properties of this particular car in your setup() and draw() functions. You could, for example, write code to change the speed property of the car - like a nitro button or something. You'd first need to create a nitro button and when pressed, call something like: myCar.speed = 10;

Setting a property: myCar.speed = 10;

Getting a property: text(myCar.speed, 25, 25);

Calling a method: myCar.drive();

Two Cars

By now you might be saying to yourself, "This seems like a whole lot of extra work for nothing." Why did we have to go through that all that extra trouble? Let me tell you - because we created a car factory, we have the ability to not only create one car - we have the ability to create two cars or millions of cars without that much overhead.

Did you know that each Rolls Royce is made by hand? Every one of them. That's why each one takes months to build and costs the consumer well over $200,000. A Hyundai Accent, assembled by mostly machines takes less than 30 hours to build and costs the consumer $14,645. An average Hyundai plant is capable of building thousands of cars each day.

So now that we have built our own little car factory. Let's create a car for me and a car for you. I simplified the code slightly by taking out the brake capability. However, I also made the code more complex by adding the ability to customize each car object.

Notice that when I create a new car, I am now passing ypos and speed properties. The constructor will use these passed properties to make custom cars. What's cool is that each car owns it's own properties. Your car is faster than my car because I tend to drive slowly and brake for squirrels and garage sales.

var myCar, yourCar;
			
function setup()
{
  createCanvas(800, 400);
  
  // create the myCar (ypos, speed)
  myCar = new Car(150, 2);
  
  // create the yourCar (ypos, speed)
  yourCar = new Car(250, 3);
}

function draw()
{
  // clear background
  background(255);
  
  //////////////////////////////////
  // myCar
  //////////////////////////////////
  
  // check the xpos of myCar
  if(myCar.xpos > width)
  {
    myCar.xpos = 0;
  } 
  
  // drive myCar
  myCar.drive();
  
  // display myCar
  myCar.display();
  
  //////////////////////////////////
  // yourCar
  //////////////////////////////////
  
  // check the xpos of yourCar
  if(yourCar.xpos > width)
  {
    yourCar.xpos = 0;
  } 
  
  // drive yourCar
  yourCar.drive();
  
  // display yourCar
  yourCar.display();
  
}

// car constructor
function Car()
{
  this.xpos = 0;
  this.ypos = 200;
  this.speed = 2;
  this.c = color(153, 102, 51);

  // drive method
  this.drive = function()
  {
    if(this.xpos > width)
    {
      this.xpos = 0;
    }
    this.xpos = this.xpos + this.speed;        
  }

  // brake method
  this.brake = function()
  {
    if(this.speed > 0)
    {
      this.speed = this.speed - 0.05;
    } else {
      this.speed = 0;
    }        
  }

  // display method
  this.display = function()
  {
    // body of the car
    fill(this.c);
    rectMode(CORNER);
    rect(this.xpos, this.ypos, 100, 50);

    // wheels
    fill(0);
    ellipse(this.xpos + 20, this.ypos + 45, 40, 40);
    ellipse(this.xpos + 80, this.ypos + 45, 40, 40);
  }
}

Many Cars

In the United States, the automobile industry caused a huge economic revolution. Jobs were created to design highways, gas stations, motels and diners. Car factories were super important. Using our car factory, we can create tons and tons of cars.

However, before we do, we need to make our code more efficient. Creating separate variables like: myCar, yourCar, hisCar, herCar, iceCreamTruck, wickedCoolLimoWithHydrolicsCar, etc is going to take forever. We need a way to do something really repetitive over and over again. Ladies and gentlemen, meet the all-important loop and it's trusty sidekick the array.

As it turns out, we can't create millions of cars without them. So, let's put our master plan to siphon up billions of dead dinosaur families from the oily pits of the Earth on hold for a moment as we learn about loops and arrays.

Loops

A loop allows you to do something again and again and again. Let's say that you woke up and had an gnawing urge to draw 50 random circles on the screen. That happens to me a lot. Sometimes, I can't help myself.

We could do the following, knowing what we know right now:

function setup()
{
  createCanvas(400, 150);

  // draw 50 circles
  fill(0);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10);
  ellipse(random(width), random(height), 10, 10); 
}

function draw()
{

}

But there is a much better way to do this using loops:

function setup()
{
  createCanvas(400, 150);

  // draw 50 circles
  fill(0);
  for(var i = 0; i < 50; i++)
  {
    ellipse(random(width), random(height), 10, 10);
  }
}

function draw()
{

}

"Hell to the yeah!!!" you just said to yourself. I know. That was totally awesome. The for loop works a little like this:

The for loop allowed us to run the ellipse(random(width), random(height), 10, 10); code fifty times.

var i = 0 tells us what counter value to start with.

i < 50 tells us when is should stop making random circles

i++ increases the counter by 1

As long, as i is less than 50 it will continue making random circles. Each time it makes a random circle, i gets incremented by one. In fact, you can try to see the value of i by printing it to your console:

function setup()
{
  createCanvas(400, 150);

  // draw 50 circles
  fill(0);
  for(var i = 0; i < 50; i++)
  {
    ellipse(random(width), random(height), 10, 10);
    println(i);
  }
}

function draw()
{

}

Go ahead and try it. Seriously, don't be lazy! Coding is a hands-on sport.

So, what does any of this have to do with car objects? Well, a loop will allow us to make lots and lots of cars really quickly. However, there is one more thing that we'll need to learn before we do that. I mean, using a for loop would allow us to simply create random cars... but, what if we wanted to name each one so that we can tell each one what we want it to do. To do this we are going to use an array.

Arrays

An array is a list of data. Each piece of data in an array is identified by an index number representing its position in the array. Arrays are zero based, which means that the first element in the array is [0], the second element is [1], and so on.

Say what? Can you explain this better?

Let's try this again. When you go to Dunkin Donuts and ask for a box of donuts, the person behind the counter doesn't simply dump 12 donuts on the counter for you to scoop up with your arms. No, the person behind the counter puts each donut into a box that you can take with you and say, "Look, I've got donuts". I love donuts. That box of donuts is an array! It's a list of data or donuts...

Each donut in the array would have it's own name and value:

// create an empty array (or box of donuts)
var donuts = [];
	
// start filling the array with donuts 
donuts[0] = "Boston Creme";
donuts[1] = "Old Fashioned";
donuts[2] = "Jelly"; // does anyone actually like jelly donuts?
donuts[3] = "Chocolate";

I could have also created this array like this:

var donuts = ["Boston Creme", "Old Fashioned", "Jelly", "Chocolate"];

Either way, in the end it doesn't matter. I've got donuts. And that makes me happy.

So now, we can use this same methodology to go to the car factory and say, "I want a box of cars" or whatever. Each car will have it's own name.

var cars = [];
			
function setup()
{
  createCanvas(800, 400);
  
  // make 25 cars
  for(var i = 0; i < 25; i++)
  {
    cars[i] = new Car();
  }
}

function draw()
{
  // clear background
  background(255);
  
  // loop through each car
  for(var i = 0; i < cars.length; i++)
  {
    cars[i].drive();
    cars[i].display();
  }      
  
}

// car constructor
function Car(ypos, speed)
{
  this.xpos = random(width);
  this.ypos = random(height);
  this.speed = random(4);
  this.c = color(random(255), random(255), random(255));

  // drive method
  this.drive = function()
  {
    if(this.xpos > width)
    {
      this.xpos = -200; // start offscreen
      this.ypos = random(height);
    }
    this.xpos = this.xpos + this.speed;        
  }

  // display method
  this.display = function()
  {
    // body of the car
    fill(this.c);
    rectMode(CORNER);
    rect(this.xpos, this.ypos, 100, 50);

    // wheels
    fill(0);
    ellipse(this.xpos + 20, this.ypos + 45, 40, 40);
    ellipse(this.xpos + 80, this.ypos + 45, 40, 40);
  }
}

Just in case you're not quite sure how each car has it's own name:

for(var i = 0; i < 25; i++)
{
  cars[i] = new Car();
}

This line will generate the equivalent of the following:

cars[0] = new Car();
cars[1] = new Car();
cars[2] = new Car();
cars[3] = new Car();
cars[4] = new Car();
cars[5] = new Car();
cars[6] = new Car();
cars[7] = new Car();
cars[8] = new Car();
cars[9] = new Car();
cars[10] = new Car();
cars[11] = new Car();
cars[12] = new Car();
cars[13] = new Car();
cars[14] = new Car();
cars[15] = new Car();
cars[16] = new Car();
cars[17] = new Car();
cars[18] = new Car();
cars[19] = new Car();
cars[20] = new Car();
cars[21] = new Car();
cars[22] = new Car();
cars[23] = new Car();
cars[24] = new Car();
comments powered by Disqus