Experiment 3: Bouncy Balls

Sections in this Article:

The Ball

This is a great time to introduce two core aspects of object orientated programming, classes and objects [not quite the same thing although this issue is sometimes confused!]. In the bouncy ball example, a class is used to describe the ball and only the ball! This includes its behaviour, size and position on the screen. For example, a ball accelerates downwards due to the pull of gravity and when it hits a boundary, some (but not all) of the energy is used to reverse the velocity of the ball e.g. a bounce. The object on the other hand, is a single instance of a ball; each time a new ball is needed, for example when the user clicks on the screen, the program uses the class definition of a ball to create a new object in memory to present that new ball.

The snippet of code below is the class that describes the ball used in the bouncy ball example:

ball.js
1class Ball {
2
3    x = 0;
4    y = 0;
5    vx = 1;
6    vy = 0;
7    radius = 10;
8    gravity = 0.1;
9    bounceLoss = 0.9;
10
11    constructor(radius, gravity, bounceLoss, boundingArea ) {
12        this.radius = radius;
13        this.gravity = gravity;
14        this.bounceLoss = bounceLoss;
15
16        this.Initialise(boundingArea);
17    }
18
19    Initialise(boundingArea) {
20        this.vx= (Math.random()*20)-10;
21        this.vy = 0;
22        this.x = (boundingArea.width * Math.random()/2) + boundingArea.width/2;
23        this.y = -this.radius*2; // start just off screen
24    }
25
26    Update(bounds) {
27        let applyGravity = true;
28        
29        // animate ball
30        this.x+=this.vx;
31        this.y+=this.vy;
32        
33        // bounds check
34        if( this.x > bounds.width-this.radius ) this.x = bounds.width-this.radius;
35
36        // following bounds detection could be made much more simpler, but user
37        // might resize bounds when ball is outside...
38        if( this.vx<0) {
39            if( (this.x-this.radius) <= 0 ) {
40                this.x = this.radius;
41                this.vx = -this.vx*this.bounceLoss;
42            }
43        } else {
44            if( (this.x+this.radius) >= bounds.width ) {
45                this.x = bounds.width-this.radius;
46                this.vx = -this.vx*this.bounceLoss;
47            }
48        }
49
50
51        if( this.vy<0) {
52            // travelling up
53            if( (this.y-this.radius) <= 0 ) {
54                this.y = this.radius;
55                this.vy = -this.vy;
56            }
57
58        } else {
59            // travelling down
60            if( (this.y+this.radius) > bounds.height ) {
61               this.y = bounds.height-(this.radius);
62                
63                // don't apply gravity if we're at rock bottom; can't accelerate downwards at this point!
64                if( Math.abs(this.vy) <2 ) { // is ground sticky, any vy < 0.8 doesn't result in a bounce!
65                    this.vy = 0;
66                    applyGravity = false;   
67                    
68                    //some friction to x
69                    this.vx *= 0.99;
70                    
71                } else {
72                    this.vy = -(this.vy*this.bounceLoss);
73                   
74                    // not a perfect bounce, introduce some vx
75                    this.vx+=(Math.random()-0.5)/4;
76                    
77                }
78            }
79        }
80
81        if(applyGravity) {
82            // simulate some gravity (not proper physics!)
83            this.vy+=0.5;
84        }
85    }
86}

While this may look horribly complicated at first glance, it really isn't. The really nice thing about a class is that the code should be self contained [ideally not coupled to anything else] and should be desisgned to be re-used over and over again. In the bouncy ball example, each individual ball that bounces around the screen is represented by a ball object with a svg circle representing the visual representation of the ball. For each new ball that is required a ball object and an svg circle need to be created. How that comes about will be discussed shortly, but the key thing to note is the ball object is not coupled to the svg circle which means, we could quite easily use a different display technology, e.g. a canvas at a later time and we wouldn't need to change anything in the Ball class - pretty neat!

But before we go into all of that, let’s look at what’s in the Ball class.

Class Definition

The class definition describes the class. In its simple form, it describes the name of the class in this case “Ball” and then the behaviour and properties/fields of the class. The entire class definition is essentially Lines 1 to 86, but Line 1 defines the class and its name – everything else between the curly brackets is the class.

Stripping everything back, in its simple form, a class looks like this:

class <name> {
  //
  // class definition
  //
}

Within the class we define two things, fields and methods. Fields are the bits of data stored in the class, for example, the radius of the ball. Methods are the things we can request the class to do, for example Update for the next frame in the animation [perhaps CalculateNextFrame would have been a better name?].

The Constructor

Classes have one special method called a constructor. This is a function that is called when an object, or ball in this example, is being created. It's purpose is basically to get everything ready in the object. In the example above, the constructor is defined on Lines 11-17. Note, the ball isn't created by this function - something else to be discussed later does this!

ball.js
11    constructor(radius, gravity, bounceLoss, boundingArea ) {
12        this.radius = radius;
13        this.gravity = gravity;
14        this.bounceLoss = bounceLoss;
15
16        this.Initialise(boundingArea);
17    }

The ball constructor takes 4 parameters, radius , gravity , bounceLoss and boundingArea .

These parameters are defined as follows:

radius – a number that defines the radius of the ball – you’ll see in the example, that balls have differing radius.

gravity – a constant that defines the downward acceleration of the ball, e.g. gravity. This isn’t really needed as all items on Earth experience the same gravity, but in this example, we ‘could’ permit each ball to experience different gravity! [Why not try hacking the code to see how this would look?]

bounceLoss – this is a factor that determines how much velocity is lost with each bounce. For example, a value of 0.9 means 90% of the velocity is conserved in a bounce. A value of greater than 1 would mean that the ball would gain more energy with each bounce! You may notice in the example, bigger balls loose more energy than the smaller balls - this is how this is achieved!

boundingArea – an object literal with properties width and height representing the size of the screen where the ball bounces [this is one of the things I don’t like about JavaScript; you have to take it on faith that the boundingArea has width and height properties, or add some defensive code in case it doesn’t].

All the constructor really does is copy the parameters into the fields of the class then initialise everything in the class ready to be animated. The initialisation is done by the function on Lines 19-24:

Class Methods

The Initialise method calculates some random x velocity for the ball (Line 20); negative values means it will drift to the left, positive values means it will drift to the right. The ball initially starts with no vertical velocity (Line 21). Line 22 positions the ball somewhere between the left and right of the boundary; which is why the boundingArea is passed in, and Line 23 positions the ball just off the top edge of the screen.

ball.js
19    Initialise(boundingArea) {
20        this.vx= (Math.random()*20)-10;
21        this.vy = 0;
22        this.x = (boundingArea.width * Math.random()/2) + boundingArea.width/2;
23        this.y = -this.radius*2; // start just off screen
24    }

The bulk of what happens to a Ball is defined in the Update method on Lines 26-85. This method should be called for each frame of the animation. There’s quite a bit going on here so we’ll break it down, but effectively, this method calculates everything ready for the next frame in the experiment.

Line 27 sets up a local variable to determine if gravity is to be applied; when the ball is in free space and falling, gravity will be applied, but when the ball is a rest, running along the bottom of the screen, no further gravity can be applied so we'd set this variable to false!

ball.js
27        let applyGravity = true;

Lines 30 and 31 calculate next new position of the ball based upon how much x velocity and how much y velocity the ball has:

ball.js
30        this.x+=this.vx;
31        this.y+=this.vy;

It’s worth noting; this is not the final position of the ball – what happens if by doing this calculation, the ball is positioned off or partially through the edge of screen? That's the purpose of the next few lines of code, they determine what happens...

Lines 33-79 are where the clever stuff happens. This code examines the position and velocity of the ball, ensures the ball remains inside the boundary (even if the boundary is re-sized) and handles bouncing off the bottom and sides of the screen. Here’s the detail...

Line 34 ensure the ball is always with the left and right boundaries.

ball.js
34        if( this.x > bounds.width-this.radius ) this.x = bounds.width-this.radius;

Lines 38-48 Look at the left/right component of the ball’s velocity. If it’s heading left (velocity x is negative) and the ball is positioned off the screen (ball centre and its radius), then it will be positioned on the edge of the screen and ‘bounced’. If the velocity is positive e.g. heading right, we do the same but for the opposite side of the screen. A bounce is achieved by negating the current x velocity and taking away a little bit due to a bounce loss (see lines 41 and 46 respectively)!

ball.js
38        if( this.vx<0) {
39            if( (this.x-this.radius) <= 0 ) {
40                this.x = this.radius;
41                this.vx = -this.vx*this.bounceLoss;
42            }
43        } else {
44            if( (this.x+this.radius) >= bounds.width ) {
45                this.x = bounds.width-this.radius;
46                this.vx = -this.vx*this.bounceLoss;
47            }
48        }

That's the left and right position of a ball sorted, now what about the up and down? This is what Lines 51-79 do:

ball.js
51        if( this.vy<0) {
52            // travelling up
53            if( (this.y-this.radius) <= 0 ) {
54                this.y = this.radius;
55                this.vy = -this.vy;
56            }
57
58        } else {
59            // travelling down
60            if( (this.y+this.radius) > bounds.height ) {
61               this.y = bounds.height-(this.radius);
62                
63                // don't apply gravity if we're at rock bottom; can't accelerate downwards at this point!
64                if( Math.abs(this.vy) <2 ) { // is ground sticky, any vy < 0.8 doesn't result in a bounce!
65                    this.vy = 0;
66                    applyGravity = false;   
67                    
68                    //some friction to x
69                    this.vx *= 0.99;
70                    
71                } else {
72                    this.vy = -(this.vy*this.bounceLoss);
73                   
74                    // not a perfect bounce, introduce some vx
75                    this.vx+=(Math.random()-0.5)/4;
76                    
77                }
78            }
79        }

Going up is the easy bit, in the unlikely event that a ball is able to bounce off the top of the screen [how can it do that? Suggestions in the comments please], then the ball is positioned at the top of the screen and it’s velocity reversed without any bounce loss – that’s all done on Lines 51-56.

Lines 59-79 consider what happens when a ball is heading downwards (see condition on Line 51). The only time something needs to be done is when the ball hits or goes through the bottom of the boundary – Line 60 detects when this happens and Line 61 ensures the ball can’t go through the bottom of the screen!

There are a few things also being simulated here that I didn’t mention earlier. The purpose of which, is to give the ball even more realism. First off; ground has a bit of a sticky effect – a ball will bounce only when it has exceeds a minimum y-velocity, if it doesn't the ball, will effectively stick to the ground and not bounce. That’s the purpose of Line 64 to test for this condition. Lines 65-69 determine what happens in this case by setting vertical velocity (up/down) to zero and cancelling gravity (Line 66) e.g. the ball can’t accelerate downward through the bottom of the screen as the bottom of the screen is pushing back-up! The ball is now effectively rolling left to right along the bottom of the screen and therefore, Line 69 introduces a bit of friction to the horizontal velocity of a ball which effectively slows it down over time.

Our Ball is almost complete. There is just one thing remaining to do; apply gravity (if applicable) to accelerate it downward. That’s done on Lines 81-84:

ball.js
81        if(applyGravity) {
82            // simulate some gravity (not proper physics!)
83            this.vy+=0.5;
84        }

Phew we're there - we've implemented a class that represents a ball but.... it doesn't do anything yet [insert sad face]. For that, we have to instantiate a ball object, create some visual representation of it and update the graphics each time we've called the Ball's Update method. This we'll be discussed in the next section...

Continue on reading to Balls! Bringing it Together...