Introduction to Objects

JavaScript supports object-oriented programming (OOP)

OOP is a way to combine multiple variables (and functions) into a single value

this greatly simplifies many programs, and is a standard technique for creating large programs

we’ll only introduce a few basic features of objects here; there is much more to learn!

as a concrete example, lets re-write this program using objects:

'use strict';

let x, y;    // position of the ball
let dx, dy;  // velocity of the ball

function setup() {
    createCanvas(500, 500).parent('p5canvas');

    // set the initial value of the ball's position and velocity
    x = 250;
    y = 250;
    dx = -1.5;  // try setting these
    dy = 1.1;   // randomly
}

function draw() {
    background(220);

    fill(255, 0, 0);
    noStroke();
    ellipse(x, y, 100, 100);

    // update the position of the ball
    x += dx;
    y += dy;
}

to start, we’ll now think of the bouncing ball as a single object that consists of multiple variables

the ball variables are things like this:

  • the (x, y) position of the ball
  • the velocity of the ball, i.e. dx and dy
  • the diameter of the ball
  • the fill-color of the ball
  • ... any other property you want your ball to have ...

here’s a version of the bouncing-ball program that uses an object named ball1:

'use strict';

let ball1;  // global variable

function setup() {
    createCanvas(500, 500).parent('p5canvas');

    ball1 = {
        x: 250,
        y: 250,
        dx: 3,
        dy: 1,
        diam: 50,
        fillColor: color(255, 0, 0)
    }
}

function draw() {
    background(200);

    fill(ball1.fillColor);
    noStroke();
    ellipse(ball1.x, ball1.y, ball1.diam, ball1.diam);

    ball1.x += ball1.dx;
    ball1.y += ball1.dy;

    // off right?
    if (ball1.x >= 500) {
        ball1.dx = -ball1.dx;
    }

    // off bottom?
    if (ball1.y >= 500) {
        ball1.dy = -ball1.dy;
    }

    // off left?
    if (ball1.x < 0) {
        ball1.dx = -ball1.dx;
    }

    // off top?
    if (ball1.y < 0) {
        ball1.dy = -ball1.dy;
    }
}

some things to note:

  • ball1 is declared as a global variable outside of any functions so that it can be accessed inside both the setup() and draw() functions

  • ball1 is initialized inside of setup() as follows:

    ball1 = {
        x: 250,
        y: 250,
        dx: 3,
        dy: 1,
        diam: 50,
        fillColor: color(255, 0, 0)
    }
    

    the code in the { and } is an example of an object literal

    this object consists of property:value pairs

    for example, x is one property of ball1, and it’s value is 250

    we can access property values of an object using dot-notation, e.g. ball1.x is refers to the x property of ball1

    we can use ball1.x just like a regular variable, as you can see in the rest if the code

  • JavaScript’s notation for objects has become an extremely popular format for sending data across the Internet, and it is referred to as JSON, which stands for JavaScript object notation

    practically all programming languages now have libraries of code for reading and writing JSON

    and so even if you don’t use JavaScript, you might run into JSON in other situations

    for instance, Minecraft uses JSON for some commands, e.g.:

    /tellraw @a {"text":"Hello","color":"red","italic":true}
    

More than One Ball

the program above is a little longer and a little more complicated than the original non-object-oriented version

and so if this is all you want to do, there’s not much value in uses objects

but where really start to shine is when your program gets bigger and more complicated

so now lets make 3 balls bounce around the screen

to do this, we’re first going to make a simple change to the one-ball program by moving most of the the code in draw() into its own function:

'use strict';

let ball1;

function setup() {
    createCanvas(500, 500).parent('p5canvas');

    ball1 = {
        x: 250,
        y: 250,
        dx: 3,
        dy: 1,
        diam: 50,
        fillColor: color(255, 0, 0)
    }
}

function draw() {
    background(200);

    handleBall1();
}

function handleBall1() {
    fill(ball1.fillColor);
    noStroke();
    ellipse(ball1.x, ball1.y, ball1.diam, ball1.diam);

    ball1.x += ball1.dx;
    ball1.y += ball1.dy;

    // off right?
    if (ball1.x >= 500) {
        ball1.dx = -ball1.dx;
    }

    // off bottom?
    if (ball1.y >= 500) {
        ball1.dy = -ball1.dy;
    }

    // off left?
    if (ball1.x < 0) {
        ball1.dx = -ball1.dx;
    }

    // off top?
    if (ball1.y < 0) {
        ball1.dy = -ball1.dy;
    }
}

now you can see how we could add another ball: we create a new object called ball2, and corresponding function called handleBall2 that draws and moves it:

'use strict';

let ball1;
let ball2;

function setup() {
    createCanvas(500, 500).parent('p5canvas');

    ball1 = {
        x: 250,
        y: 250,
        dx: 3,
        dy: 1,
        diam: 50,
        fillColor: color(255, 0, 0)
    }

    ball2 = {
        x: 150,
        y: 250,
        dx: -1,
        dy: 2,
        diam: 50,
        fillColor: color(0, 255, 0)
    }
}

function draw() {
    background(200);

    handleBall1();
    handleBall2();
}

function handleBall1() {
    fill(ball1.fillColor);
    noStroke();
    ellipse(ball1.x, ball1.y, ball1.diam, ball1.diam);

    ball1.x += ball1.dx;
    ball1.y += ball1.dy;

    // off right?
    if (ball1.x >= 500) {
        ball1.dx = -ball1.dx;
    }

    // off bottom?
    if (ball1.y >= 500) {
        ball1.dy = -ball1.dy;
    }

    // off left?
    if (ball1.x < 0) {
        ball1.dx = -ball1.dx;
    }

    // off top?
    if (ball1.y < 0) {
        ball1.dy = -ball1.dy;
    }
}

function handleBall2() {
    fill(ball2.fillColor);
    noStroke();
    ellipse(ball2.x, ball2.y, ball2.diam, ball2.diam);

    ball2.x += ball2.dx;
    ball2.y += ball2.dy;

    // off right?
    if (ball2.x >= 500) {
        ball2.dx = -ball2.dx;
    }

    // off bottom?
    if (ball2.y >= 500) {
        ball2.dy = -ball2.dy;
    }

    // off left?
    if (ball2.x < 0) {
        ball2.dx = -ball2.dx;
    }

    // off top?
    if (ball2.y < 0) {
        ball2.dy = -ball2.dy;
    }
}

the handleBall2 function is simply a copy of handleBall1, but with every occurrence of ball1 replace by ball2 (which is easy to do in a text editor)

adding a 3rd (or 4th or 5th) ball an be done in the same way

Simplifying the Code with Functions

there’s a lot of repeated code in the above program, and we shorten it quite a bit by using functions, e.g.:

let ball1;
let ball2;
let ball3;

// creates and returns a newly created random ball object
function makeRandomBall() {
    return {
        x: random(500),
        y: random(500),
        dx: random(-5, 5),
        dy: random(-5, 5),
        diam: random(10, 100),
        fillColor: color(random(256), random(256), random(256))
    }
}

function setup() {
    createCanvas(500, 500).parent('p5canvas');

    ball1 = makeRandomBall();
    ball2 = makeRandomBall();
    ball3 = makeRandomBall();
}

function draw() {
    background(200);

    handleBall(ball1);
    handleBall(ball2);
    handleBall(ball3);
}

function handleBall(ball) {
    fill(ball.fillColor);
    noStroke();
    ellipse(ball.x, ball.y, ball.diam, ball.diam);

    ball.x += ball.dx;
    ball.y += ball.dy;

    // off right?
    if (ball.x >= 500) {
        ball.dx = -ball.dx;
    }

    // off bottom?
    if (ball.y >= 500) {
        ball.dy = -ball.dy;
    }

    // off left?
    if (ball.x < 0) {
        ball.dx = -ball.dx;
    }

    // off top?
    if (ball.y < 0) {
        ball.dy = -ball.dy;
    }
}

things to note:

  • the makeRandomBall function creates a brand new ball object (with random values), and returns that value to wherever it gets called in the program

    a function that creates an object is sometimes called a constructor

  • we have only one handleBall(ball) function now; it takes the ball we want to handle as its input parameter

  • it is now very easy to add more balls

however, if we wanted to have, say, 100 different balls, we’d have a program consisting of more then 300 lines of code, including:

  • 100 let statements:

    let ball1;
    let ball2;
    let ball3;
    // ...
    let ball100;
    
  • 100 makeRandomBall statements:

    ball1 = makeRandomBall();
    ball2 = makeRandomBall();
    ball3 = makeRandomBall();
    // ...
    ball100 = makeRandomBall();
    
  • 100 handleBall statements:

    handleBall(ball1);
    handleBall(ball2);
    handleBall(ball3);
    // ...
    handleBall(ball100);
    

this is clearly impractical!

to do better, we need to more programming concepts: loops and arrays