5.9
Image from the w5_03 sketch
The w5_03 sketch

Moving balls the object-oriented way

Let’s look at some sample sketches in Processing that make use of objects.

Open and run the w5_03 sketch. This sketch builds on the basic class definition used in the foldout on OOP. It creates an array of objects of the class Ball that move around on the screen. Each ball is initialised with a random position and size. Note that the size uses a Gaussian distribution with a mean of 20 and a standard deviation of 4.

float ballSize = constrain(20 + (randomGaussian() * 4), 1, 100);

The constrain() function is used to constrain the values returned to the limits shown, even though values near these limits are extremely unlikely.

The code inside the draw() function is very simple. It just clears the screen to black and then iterates over each Ball object using a for loop, telling each to move() and then display() itself.

Let’s take a closer look at the ‘class_ball’ tab.

You can see the class definition for Ball here. The instance variables store the position, size, speed and direction of travel for each object. The variable omega stores the rate of change of direction for the ball. If omega is 0 the ball moves in a straight line. Any non-zero value will cause the ball to move in a circle – the higher the value the smaller the radius.

Notice the constructor creates a ball at the supplied position and size. Other values are set to 0. Its important to initialise all variable in your construction, because after construction your object needs to be in an initialised state, otherwise it may behave irregularly or crash the program.

You might wonder why we don’t allow initialisation of the other instance variables (speed, direction, omega) via constructor parameters. We could do this, but it makes the number of arguments that need to be supplied quite long.

It is also possible to do what’s called overloading the constructor function and in fact have multiple constructor functions for the one class. Each new constructor function must have a different set of parameters.

For example, we could add another constructor (the default constructor) that allows a Ball to be initialised without any arguments.

Ball() {
    x = y = 0;
    size = 1;
    speed = direction = omega = 0;
}

As your programs become more advanced, its a good idea to provide this default constructor, because it can be called implicitly during certain operations. Another type of constructor is called the copy constructor that is called when one object is copied into another.

The randomiseDirection() method does as the name implies: it randomises the direction and speed of the ball’s travel.

move() moves the ball by a small amount in the current direction of travel, based on the speed. This function is designed to be called every frame. This function also calls checkBounds() which tests if the centre of the ball has hit the edges of the display window. If so, the direction changes 180 degrees. Notice the use of this statement:

direction = direction % 360;

Do you know what it does and why? It seems to be constraining the direction to lie between 0 and 360 degrees. Is there anything wrong with doing it using constrain() as shown below?

direction = constrain(direction, 0, 360);

If you’re unsure, change the line in the sketch and observe the results.

Modifying the sketch

Code a new version of the sketch that has the following enhancements:

  1. For each ball allow the option of displaying the current speed and direction as a ‘vector’ arrow that points in the direction of travel, with a length proportional to the speed.

  2. Give each ball the ability to also show its speed and direction as text displayed in the centre of each ball.

Each of these features should be able to be switched on and off using keyboard commands. If you have time, you might like to try other enhancements, for example, giving each ball it’s own colour based on speed and direction of travel. A reasonable mapping is direction to hue, speed to brightness and turn rate to saturation.

You could also modify the behaviour so that each ball changes direction when it intersects with another. One way to do this is to create a new member function, intersect():

boolean intersect(Ball anotherBall) {
    return dist(x,y, anotherBall.x, anotherBall.y) < (size + anotherBall.size);
}

As you can see, it is possible to access the instance variables of a class. This direct access is possible, but often dangerous, particularly if you change an object’s instance variable from outside of the object (eg. saying something like anotherBall.x = 0;). Dangerous because you’re changing the object’s state without the object ‘knowing’ about it.

A safer solution is to provide ‘getter’ and ‘setter’ methods…but this is a more advanced topic for another time.


Share this article:

This article is from the free online course:

Creative Coding

Monash University

Get a taste of this course

Find out what this course is like by previewing some of the course steps before you join: