1. Object Oriented Programming

In this exploration, we examine the idea of an object, and begin writing our first object-oriented programming study.

1.1 Old Ball Code

This code is from the discussion on Bouncing Balls. We need to keep track of the ball's velocities in the x and y direction, keep track of where it is (x, y), and the global time.

In [6]:
float g = 9.8; 
float dt;  
float t;

float vx1;
float vy1;
float x1;
float y1;

float vx2;
float vy2;
float x2;
float y2;

void setup() {
    size(200, 500);
    dt = 0.1;  
    t = 0;
    x1 = width/2;
    y1 = 50;
    vx1 = 50.0;
    vy1 = 0.0;
    x2 = width/2;
    y2 = 25;
    vx2 = -50.0;
    vy2 = -10.0;
}

void drawBall(float x, float y, int w, int h) {
    fill(255, 0, 0);
    ellipse(x, y, w, h);
}

void draw() {
    background(200);
    // gravity
    vy1 = vy1 + g * dt;
    
    float dx = vx1 * dt;    
    if (((x1 + dx) > width) || ((x1 + dx) < 0)) {
        vx1 = vx1 * -0.8;
    } else {
        x1 = x1 + dx;
    }

    vy2 = vy2 + g * dt;
    
    dx = vx2 * dt;    
    if (((x2 + dx) > width) || ((x2 + dx) < 0)) {
        vx2 = vx2 * -0.8;
    } else {
        x2 = x2 + dx;
    }

    
    float dy = vy1 * dt;
    if (((y1 + dy) > height) || ((y1 + dy) < 0)) {
        vy1 = vy1 * -0.8;
    } else {
        y1 = y1 + dy;
    }

    dy = vy2 * dt;
    if (((y2 + dy) > height) || ((y2 + dy) < 0)) {
        vy2 = vy2 * -0.8;
    } else {
        y2 = y2 + dy;
    }

    drawBall(x1, y1, 10, 10);
    drawBall(x2, y2, 15, 15);
    
    t = t + dt;
}
Sketch #6:

Sketch #6 state: Loading...
In [11]:
float g = 9.8; 
float dt;  
float t;

class Ball {
    float x;
    float y;
    float vx;
    float vy;
    float w;
    float h;
    
    Ball() {
        x = width/2;
        y = 50;
        vx = 50.0;
        vy = 0.0;
        w = 10;
        h = 10;
    }
    
    void move() {
        vy = vy + g * dt;

        float dx = vx * dt;    
        if (((x + dx) > width) || ((x + dx) < 0)) {
            vx = vx * -0.8;
        } else {
            x = x + dx;
        }
        float dy = vy * dt;
        if (((y + dy) > height) || ((y + dy) < 0)) {
            vy = vy * -0.8;
        } else {
            y = y + dy;
        }
    }

    void draw() {
        fill(255, 0, 0);
        ellipse(x, y, w, h);
    }    
}

Ball pluto = new Ball();

void setup() {
    size(200, 500);
    dt = 0.1;  
    t = 0;
}

void draw() {
    background(200);
    t = t + dt;
    pluto.move();
    pluto.draw();
}
Sketch #9:

Sketch #9 state: Loading...

What if we wanted to add more balls? We would need to add variables for vx, vy, x, y... for each ball!

1.2 Bouncing Ball as an Object

A better way is to let each ball keep track of its own properties. We do this by making a class definition, and storing each set of variables inside the Ball object.

Here we will move all of the ball-specific variables "into the Ball class" and leave the others.

Four steps for turning regular code into Object-Oriented code:

  1. Define a Ball "object" using the word class
  2. Move all global variables dealing with ball inside class
  3. Move all functions inside class. These are then called methods
    1. There is a special method of the same name of the class called the constructor
    2. Use the word this to represent the current object
  4. Create an instance of the object using the work new

We define a class, create an instance of a Ball, and "bounce" it.

In [ ]:
// Globals:

float g = 9.8;   // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball ball; // the ball

class Ball {
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;

    // Jargon: the "constructor"
    Ball(float x, float y, int w, int h, float vx, float vy) {
        // Idea: "this" refers to the current ball
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
    }
    
    // Jargon: an object's function is called a "method"
    void moveYourself() {
        this.vy = this.vy + g * dt;

        float dx = this.vx * dt;    
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }

        float dy = this.vy * dt;
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void drawYourself() {
        fill(255, 0, 0);
        ellipse(this.x, this.y, this.w, this.h);
    }
}

void setup() {
    size(200, 500);
    // Jargon: we create an "instance" of the Ball class:
    ball = new Ball(width/2, 50, 10, 10, 50.0, 0.0);
    t = 0;
}

void draw() {
    background(200);
    ball.moveYourself();
    ball.drawYourself();
    
    t = t + dt;
}

If we wanted to have two (or more) we need only create more instances:

In [ ]:
// Globals:

float g = 9.8;   // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball ball0; // the balls
Ball ball1; 

class Ball {
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;

    // Jargon: the "constructor"
    Ball(float x, float y, int w, int h, float vx, float vy) {
        // Idea: "this" refers to the current ball
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
    }
    
    // Jargon: an object's function is called a "method"
    void moveYourself() {
        this.vy = this.vy + g * dt;

        float dx = this.vx * dt;    
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }

        float dy = this.vy * dt;
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void drawYourself() {
        fill(255, 0, 0);
        ellipse(this.x, this.y, this.w, this.h);
    }
}

void setup() {
    size(200, 500);
    // Jargon: we create an "instance" of the Ball class:
    ball0 = new Ball(width/2, 50, 10, 10, 50.0, 20.0);
    ball1 = new Ball(width/4, 50, 10, 10, -29.0, -5.0);
    t = 0;
}

void draw() {
    background(200);
    ball0.moveYourself();
    ball0.drawYourself();
    
    ball1.moveYourself();
    ball1.drawYourself();
    
    t = t + dt;
}

What if we wanted 10? Or 1,000?

1.3 Introducing Arrays

To allow a large number of items, we use arrays.

Using an array takes three steps:

  1. We use Ball[] balls; as the type to define the array
  2. We need to create space for each ball: balls = new Ball[1];
  3. We then create the instances of each ball in the array: new Ball(...);
In [ ]:
// Globals:

float g = 9.8;   // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball[] balls; // the balls

class Ball {
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;

    // Jargon: the "constructor"
    Ball(float x, float y, int w, int h, float vx, float vy) {
        // Idea: "this" refers to the current ball
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
    }
    
    // Jargon: an object's function is called a "method"
    void moveYourself() {
        this.vy = this.vy + g * dt;

        float dx = this.vx * dt;    
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }

        float dy = this.vy * dt;
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void drawYourself() {
        fill(255, 0, 0);
        ellipse(this.x, this.y, this.w, this.h);
    }
}

void setup() {
    size(200, 500);
    balls = new Ball[1];
    // Jargon: we create an "instance" of the Ball class:
    balls[0] = new Ball(width/2, 50, 10, 10, 50.0, 0.0);
    t = 0;
}

void draw() {
    background(200);
    balls[0].moveYourself();
    balls[0].drawYourself();
    
    t = t + dt;
}

It is fairly easy to add more balls:

  1. Create more space in the array
  2. Create more instances
  3. Make sure we call move and update for each ball
In [ ]:
// Globals:

float g = 9.8;   // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball [] balls; // the balls

class Ball {
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;

    // Jargon: the "constructor"
    Ball(float x, float y, int w, int h, float vx, float vy) {
        // Idea: "this" refers to the current ball
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
    }
    
    // Jargon: an object's function is called a "method"
    void moveYourself() {
        this.vy = this.vy + g * dt;

        float dx = this.vx * dt;    
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }

        float dy = this.vy * dt;
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void drawYourself() {
        fill(255, 0, 0);
        ellipse(this.x, this.y, this.w, this.h);
    }
}

void setup() {
    size(200, 500);
    balls = new Ball[2];
    // Jargon: we create an "instance" of the Ball class:
    balls[0] = new Ball(width/2, 50, 10, 10, 50.0, 0.0);
    balls[1] = new Ball(width/4, 50, 10, 10, 50.0, 0.0);
    t = 0;
}

void draw() {
    background(200);
    balls[0].moveYourself();
    balls[0].drawYourself();

    balls[1].moveYourself();
    balls[1].drawYourself();
    
    t = t + dt;
}

Properties of Arrays

It can be even easier!

Make code simpler:

  • Arrays have the property .length
  • Use for-loop to move and draw all of the balls
In [ ]:
// Globals:

float g = 9.8;   // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball[] balls; // the balls

class Ball {
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;
    color c;

    // Jargon: the "constructor"
    Ball(float x, float y, int w, int h, float vx, float vy) {
        // Idea: "this" refers to the current ball
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
        this.c = color(random(255), random(255), random(255));
    }
    
    // Jargon: an object's function is called a "method"
    void moveYourself() {
        this.vy = this.vy + g * dt;

        float dx = this.vx * dt;    
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }

        float dy = this.vy * dt;
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void drawYourself() {
        fill(this.c);
        ellipse(this.x, this.y, this.w, this.h);
    }
}

void setup() {
    size(200, 500);
    balls = new Ball[200];
    // Jargon: we create an "instance" of the Ball class:
    for (int i = 0; i < balls.length; i++) {
        balls[i] = new Ball(width * random(1), height * random(1), 10, 10, random(50) - 25, 0.0);
    }
    t = 0;
}

void draw() {
    background(128);
    for (int i = 0; i < balls.length; i++) {
        balls[i].moveYourself();
        balls[i].drawYourself();
    }
    t = t + dt;
}

1.4 Variations

Cut and paste the above code and try some variations:

  1. More than one ball
  2. Add color to the balls
  3. Add a ball where you click the mouse

1.5 Collision Detection

It would be cool if the balls could detect when they run into each other, just like it detects when it hits a wall. To do that, we go through the other balls, and identify when they "overlap".

Then, we simply swap their velocities.

In [ ]:
// Globals:

float g = 9.8;  // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball [] balls; // the balls

float distance(float x1, float y1, float x2, float y2) {
    return sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
}

class Ball {
    color c;
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;

    Ball(color c, float x, float y, int w, int h, float vx, float vy) {
        this.c = c;
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
    }
    
    void move() {
        for (int i = 0; i < balls.length; i++) {
            if (balls[i] != this) {
                if (distance(this.x, this.y, balls[i].x, balls[i].y) < (this.w/2 + balls[i].w/2)) {
                    float temp = this.vx;
                    this.vx = balls[i].vx;
                    balls[i].vx = temp;
                    temp = this.vy;
                    this.vy = balls[i].vy;
                    balls[i].vy = temp;
                    break;
                }
            }
        }
        this.vy = this.vy + g * dt;      
        float dx = this.vx * dt;    
        float dy = this.vy * dt;
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void draw() {
        fill(this.c);
        ellipse(this.x, this.y, this.w, this.h);
    }
}

void setup() {
    size(200, 500);
    balls = new Ball[10];
    for (int i = 0; i < balls.length; i++) {
        balls[i] = new Ball(color(random(255), random(255), random(255)), random(width), random(40), 10, 10, random(50), 0.0);
    }
    t = 0;
}

void draw() {
    background(128);
    for (int i = 0; i < balls.length; i++) {
        balls[i].move();
        balls[i].draw();
    }
    t = t + dt;
}

Variations:

  1. Create different kinds of balls (e.g., different shapes... just use your previous drawObject here)
  2. Make the Physics more realistic (e.g., add some loss of energy, randomness)
  3. Create them where you click the mouse.
  4. Take into account the balls' masses (e.g., like this
  5. Have some things in the environment that don't move (gravity should not effect them, and they have no velocities)
  6. Have some locations that inject velocities (e.g., shoot objects into the air)
In [ ]:
// Globals:

float g = 9.8;  // gravity
float dt = 0.1;  // change in time
float t = 0;     // current time

Ball [] balls; // the balls

float distance(float x1, float y1, float x2, float y2) {
    return sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
}

class Ball {
    color c;
    float x;
    float y;
    int w;
    int h;
    float vx;
    float vy;

    Ball(color c, float x, float y, int w, int h, float vx, float vy) {
        this.c = c;
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.vx = vx;
        this.vy = vy;
    }
    
    void move() {
        for (int i = 0; i < COUNT; i++) {
            if (balls[i] != this) {
                if (distance(this.x, this.y, balls[i].x, balls[i].y) < (this.w/2 + balls[i].w/2)) {
                    float temp = this.vx;
                    this.vx = balls[i].vx;
                    balls[i].vx = temp;

                    temp = this.vy;
                    this.vy = balls[i].vy;
                    balls[i].vy = temp;
                    break;
                }
            }
        }
        this.vy = this.vy + g * dt;      
        float dx = this.vx * dt;    
        float dy = this.vy * dt;
        if (((this.x + dx) > width) || ((this.x + dx) < 0)) {
            this.vx = this.vx * -0.8;
        } else {
            this.x = this.x + dx;
        }
        if (((this.y + dy) > height) || ((this.y + dy) < 0)) {
            this.vy = this.vy * -0.8;
        } else {
            this.y = this.y + dy;
        }
    }
    
    void draw() {
        fill(this.c);
        ellipse(this.x, this.y, this.w, this.h);
    }
}

void setup() {
    size(200, 500);
    balls = new Ball[100];
    COUNT = 0;
    t = 0;
}

int COUNT = 0;

void mousePressed() {
    balls[COUNT] = new Ball(color(random(255), random(255), random(255)), 
                            mouseX, mouseY, 10, 10, random(50) - 25, 0);
    COUNT++;
}

void draw() {
    background(128);
    for (int i = 0; i < COUNT; i++) {
        balls[i].move();
        balls[i].draw();
    }    
    t = t + dt;
}