Object Oriented Modeling - Overview
Extending the Jitter Class
Understanding the class structure
-
Custom Constructors Challenge
Understanding constructors and applying complex classes
-
Eye Class Extension
Further exercises on classes
Working with Mulitple Elements on Screen
So far, when we wanted several versions of a function to run we
needed to create a set of related global variables to store
their states. For example, a series of arrays would be needed to
store x, y and maybe a size state and they all needed to be
accessed via their corresponding index. So something like this
would not be uncommon to see
ellipse(x[i], y[i], size[i]).
Each index position
(i) corresponds with a saved coordinate and size.
let x = [];
let y = [];
let size = [];
function setup() {
createCanvas(windowWidth, windowHeight);
for(let i = 0; i < 3; i++) {
x[i] = random(width);
y[i] = random(height);
size[i] = random(50,100);
}
}
function draw() {
for(let i = 0; i < 3; i++) {
ellipse(x[i], y[i], size[i]);
}
}
Working with Mulitple Elements on Screen
Let's say that we want multiple target functions with different parameter values changing over time, we would have to declare a few array variables to store all of this changing data. This is fine in the context of the target function as it does not require many variables however when working with complex functions this quickly becomes an onerous task to update and maintain so many related arrays. This is where classes become quite useful.
let x = [];
let y = [];
let size = [];
function setup() {
createCanvas(windowWidth, windowHeight);
for(let i = 0; i < 6; i++) {
// Initalise state for target
x[i] = random(width);
y[i] = random(height);
size[i] = random(50,100);
}
}
function draw() {
for(let i = 0; i < 6; i++) {
target(x[i], y[i], size[i],5);
// Update Coordinates
x[i]+=random(-1,1);
y[i]+=random(-1,1);
}
}
// Target function
Object Oriented Modelling
An Object-oriented approach makes the process of managing variables and functions simpler at a larger scale. Object-oriented programming uses what we call classes as blueprints for complex elements with several behaviors or "methods". A class is a definition of methods (functions) and variables and an object is a single instance of a class. We will be using three terms, method, object, and class a lot so its important to understand the definition of each.
For our purposes, all Classes require a constructor and at least one method.
We will expand upon this in further slides
// Create the class
class example {
// think of the constructor as the 'setup' of our class
// this runs once and this is where we declare object-specific variables
constructor() {
// Numbers specific to our object are declared using the this notation
// This means that the number is specific to that specific object
this.x = random(width);
this.y = random(height);
}
// this is where we write logic or render to screen
method() {
// we can use our object specific numbers
ellipse(this.x, this.y, 50);
}
}
Apple Class Example
- Name: Apple
- Fields: X coordinate, Y coordinate, Size
- Methods:
grow()
,fall()
,rot()
The
grow()
method that increases the size field of the apple when it is
spawned in the environment.
The
fall()
method is used to check the size and once above a randomly
decided size Once this is true the apple will stop growing and
fall to the ground.
The
rot()
method checks if the apple is on the ground and begins to
decrease the value of the size field and change the colour to
black.
Once this process is complete the apple object is removed and replaced with a new one.
Creating a Class
Here we will walk through how to structure a class. We will start basic so we can focus on the fundamentals structures of a class. In the example below, we have an ellipse with a random position and size. Let us create an equivalent but with a class structure.
// Global variables used to store the ellipse state
let x, y;
let diameter = 100;
function setup() {
createCanvas(windowWidth, windowHeight);
noStroke();
x = random(diameter, width - diameter);
y = random(diameter, height- diameter);
}
function draw() {
background(230);
ellipse(x, y, diameter, diameter);
}
Defining Our Class
Here is what our class looks like to achieve the same result as the
previous slide. We have called our class spot and given it its own
functions which we are calling using the dot syntax. We have
effectively grouped all of the related variables and functions into
a single class called is
Spot
. In this example, all values required by the
display method are created in the constructor. You'll notice a new
syntax, the
this
keyword.
class Spot {
// This is our constructor where all parameters are defined
constructor() {
// Assign random to diameter
this.diameter = random(50,100);
// Assign random number based on diameter
this.x = random(this.diameter, width - this.diameter);
// Assign random number based on diameter
this.y = random(this.diameter, height - this.diameter);
}
// here we are defining our method
display() {
fill(50);
ellipse(this.x, this.y, this.diameter, this.diameter);
}
}
This. Keyword
A new bit of syntax that will use in the context of classes is
this
keyword. When we use it in our class we are
referring to the instance of the variable that belongs to that
current object being run. For example, in the snippet of code below,
we create variables x, y, and diameter. These are referenced by the
display method, in the ellipse function. So if we have multiple
instances of the Spot class all of the random numbers created for it
are used to draw to screen.
class Spot {
constructor() {
this.diameter = random(50,100);
this.x = random(this.diameter, width - this.diameter);
this.y = random(this.diameter, height - this.diameter);
}
display() {
fill(50);
// this. referes to the varaibles declared for this instance of the object
ellipse(this.x, this.y, this.diameter, this.diameter);
}
}
Creating an Object with our Spot Class
Here is what the syntax looks like when we create an object of a class and call upon a method to draw onto the screen.
// Declare a variable to store an object
let spot1;
function setup() {
createCanvas(windowWidth, windowHeight);
noStroke();
// Construct the object
spot1 = new Spot();
}
function draw() {
background(0);
// Using the dot operator call the display method in the spot1 object
spot1.display();
}
Dot Syntax
The fields and methods of an object are accessed with the dot
operator. The syntax
spot1.diameter
accesses the value of the diameter field
inside the spot1 object. To run the display()
method
inside the spot1 object, the syntax spot.display()
is
used.
let spot1;
function setup() {
createCanvas(windowWidth, windowHeight);
noStroke();
spot1 = new Spot();
}
function draw() {
background(230);
// Run the display method of spot1
spot1.display();
// returns the requested parameter
let spotSize = spot1.diameter;
}
// Spot class
Extending the Spot Class
The Spot class can now be extended to support more behaviors by
adding speed and direction fields to maintain information about
movement and adding a
move()
method to update the position of a Spot object
when called.
class Spot {
constructor() {
this.diameter = random(50,100);
this.x = random(this.diameter, width - this.diameter);
this.y = random(this.diameter, height - this.diameter);
this.speed = random(3);
}
display() {
fill(50);
ellipse(this.x, this.y, this.diameter, this.diameter);
}
// This method will make our spot bounce off the window edges
move() {
// Updates the objects x with the objects speed
this.x += this.speed;
// Tests to see if the ellipse has touched the size
if ((this.x > (width - this.diameter / 2)) || (this.x < this.diameter / 2)) { // Reverses the direction if true
this.speed *=-1;
}
}
}
Spot Class Extended
let spot1;
function setup() {
createCanvas(windowWidth, windowHeight);
noStroke();
spot1 = new Spot();
}
function draw() {
background(230);
spot1.display();
}
class Spot {
constructor() {
this.diameter = random(50,100);
this.x = random(this.diameter, width - this.diameter);
this.y = random(this.diameter, height - this.diameter);
this.speed = random(3);
}
display() {
fill(50);
ellipse(this.x, this.y, this.diameter, this.diameter);
}
move() {
this.x += this.speed;
if ((this.x > (width - this.diameter / 2)) || (this.x < this.diameter / 2)) {
this.speed *=-1;
}
}
}
Arrays of Objects
Working with arrays of objects is similar to working with arrays of other data types. Because each array element is an object, each element of the array must be created before it can be accessed. The steps for working with an array of objects are:
- Declare the array globally
- Create a new object for each position in the array
- Call the required methods for that object to run as intended
let spot = [];
function setup() {
createCanvas(windowWidth, windowHeight);
noStroke();
// Filling the array with spot objects
for (let i = 0; i < 3; i++) {
spot[i]=new Spot();
}
}
function draw() {
background(230);
// Use a for loop to run through every object method
for (let i=0; i < spot.length; i++) {
spot[i].display();
spot[i].move();
}
}
Defining Our Constructor Parameters
In this example, we have set a parameter for xPos, yPos and dia notice how these numbers are then allocated to specific variables declared inside the class.
class Spot {
// All of these parameters get applied to our class fields
// When we create a new object.
constructor(xPos, yPos, dia, speed) {
this.x = xPos; // Assign xpos to x
this.y = yPos; // Assign ypos to y
this.diameter = dia; // Assign dia to diameter
this.speed = speed;
}
display() {
fill(50);
ellipse(this.x, this.y, this.diameter, this.diameter);
}
// This method will make our spot bounce off the window edges
move() {
this.x += this.speed;
if ((this.x > (width - this.diameter / 2)) || (this.x < this.diameter / 2)) {
this.speed *=-1;
}
}
}
Defining Our Constructor Parameters
Now that we have custom constructor parameters, when creating a new object we also need to specify these numbers. In our example, we need to set a coordinate, size and speed to create a new Spot object. Custom parameters are helpful when you want to set up your class in a certain way.
let spot;
function setup() {
createCanvas(windowWidth, windowHeight);
noStroke();
let size = random(50,100);
let speed = random(3);
spot = new Spot(random(size, width - size), random(size, height - size), size, speed);
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
setup();
}
function draw() {
background(230);
spot.display();
spot.move();
}
// Spot Class
Updating Object Methods with External Values
It's fairly common that you will need to have contextual information only available in the draw loop being required by a specific method in your class. The syntax for this is familiar. Here we use the eyes class as an example, this class requires a current mouseX and mouseY coordinate to function properly.
// In the draw loop
for (var i = 0; i < eyes.length; i++) {
eyes[i].update(mouseX, mouseY);
eyes[i].display();
}
// In the eyes class
// The update method has the parameters it needs declared in its parentheses
update(mouseX, mouseY) {
// etc
// etc
}
ES6 vs ES5
We are teaching a more current version of javascript which is called ES6 while watching older tutorial videos you'll notice that there is a subtle difference between what you'll see in class and the content you'll consume. Here is a side by side comparison of ES6 and ES5 so you can see the difference between the two. At this current point in time, both of these syntaxes work just fine.
Newer
class Spot {
// All of these parameters get applied to our class fields
// When we create a new object.
constructor(xPos, yPos, dia, speed) {
this.x = xPos; // Assign xpos to x
this.y = yPos; // Assign ypos to y
this.diameter = dia; // Assign dia to diameter
this.speed = speed;
}
display() {
fill(50);
ellipse(this.x, this.y, this.diameter, this.diameter);
}
// This method will make our spot bounce off the window edges
move() {
this.x += this.speed;
if ((this.x > (width - this.diameter / 2)) || (this.x < this.diameter / 2)) {
this.speed *=-1;
}
}
}
Older
// Spot would be defined as a function with the parameters being declared here as well.
function Spot(xpos, ypos, dia, speed) {
// the constructor isn't declare and fields are
// just stated inside the spot class block
this.x = xPos;
this.y = yPos;
this.diameter = dia;
this.speed = speed;
this.display = function() {
fill(50);
ellipse(this.x, this.y, this.diameter, this.diameter);
}
// This method will make our spot bounce off the window edges
this.move = function() {
this.x += this.speed;
if ((this.x > (width - this.diameter / 2)) || (this.x < this.diameter / 2)) {
this.speed *=-1;
}
}
}