Special Effect: Throwing a Ball¶
In these notes you will learn:
- How to “throw” a ball by flicking it with the mouse.
Introduction¶
In this note we’ll write a program that lets you “throw” a ball using the mouse. It is an instructive example that combines two things we’ve already seen: moving a ball and drag-and-drop.
Planning the Program¶
Our goal is to make a small demo program that lets a user grab and throw a ball (in 2 dimensions). The ball moves in the thrown direction at a constant speed proportional to the speed the ball was thrown at. That is, the faster you throw the ball, the faster it should move.
As we’ve seen, we can represent the speed of an object using dx
and dy
variables. When we throw the ball, we will need to set the values of dx
and dy
automatically and in a way that depends upon the speed and
direction the mouse pointer is moving during the throwing motion. The simplest
way is to subtract the previous mouse pointer position from the current one,
e.g.:
dx = mouseX - pmouseX;
dy = mouseY - pmouseY;
Recall that pmouseX
and pmouseY
are special Processing
variables that store the position of the mouse pointer on the
previous call to draw.
We also need to think about where to put these lines of code. Throwing the
ball consists of three distinct phases: picking up the ball, throwing it by
moving the pointer, and then letting it go by releasing the mouse button. So
the moment we release the mouse button is when we have enough information to
calculate dx
and dy
. Fortunately, Processing has a special function
named mouseReleased()
that is called just when a mouse button is released,
and so that’s where we put the code to calculate the ball’s velocity:
void mouseReleased() {
dx = mouseX - pmouseX;
dy = mouseY - pmouseY;
}
The Program¶
The rest of the program is essentially a combination of ball-bouncing and ball-dragging code that we’ve already seen:
float x, y; // center of the ball
float diam;
float dx, dy; // velocity of the ball
void setup() {
size(500, 500);
smooth();
x = 250;
y = 250;
diam = 60;
dx = 0;
dy = 0;
}
void draw() {
background(255);
noStroke();
fill(200, 0, 0);
ellipse(x, y, diam, diam);
x += dx;
y += dy;
// has ball hit the left edge?
if (x - diam / 2 < 0) {
x = diam / 2;
dx = -dx;
}
// has ball hit the right edge?
if (x + diam / 2 >= 500) {
x = 500 - diam / 2;
dx = -dx;
}
// has ball hit the top edge?
if (y - diam / 2 < 0) {
y = diam / 2;
dy = -dy;
}
// has ball hit the bottom edge?
if (y + diam / 2 >= 500) {
y = 500 - diam / 2;
dy = -dy;
}
}
void mouseDragged() {
if (pointInCircle(mouseX, mouseY, x, y, diam / 2)) {
dx = 0;
dy = 0;
x += mouseX - pmouseX;
y += mouseY - pmouseY;
}
}
void mouseReleased() {
dx = mouseX - pmouseX;
dy = mouseY - pmouseY;
println("mouse thrown: dx = " + dx + ", dy = " + dy);
}
boolean pointInCircle(float x, float y, float a, float b, float r) {
if (dist(x, y, a, b) <= r) {
return true;
} else {
return false;
}
}
This works! Playing with this program will give you a better idea of how the
mouseDragged()
function works. For instance, notice that if you hold the
mouse button down while the ball is bouncing, the pointer will “catch” the
ball when it hits it. Also, you can only grab the ball by clicking and
moving; just clicking on the ball is not enough to grab it in this program.
A Small Problem¶
It’s possible to play with this program for a while not notice it has a major flaw.
The problem is that you can throw the ball without holding it! Try it and see: click on the screen but not on the ball, and then move and release the mouse as if you were throwing the ball. Magically the ball starts moving in your throwing direction. While this is may be what you want in some programs, for us it’s a serious bug that we need to fix. You should only be able to throw the ball while you are holding it.
Note
This is a good example of how a major bug can go undetected. It’s only noticeable if you perform the unnatural action of throwing the ball without actually clicking on it. There’s no real reason why you would ever do this, and so it could stay hidden for a long time.
How can we fix this? If you think about it, we only want the code in
mouseReleased()
to be called when the mouse is being dragged. For any
other click we should not set dx
and dy
. So we need to be able to
tell when the ball is being dragged.
One way of doing this is to add a boolean
flag variable called
dragging
that we will set to true
just when the ball is being dragged.
Then in mouseReleased()
we will check that dragging
is true
before
running any code.
Here are the changes to the program:
boolean dragging; // global variable
void setup() {
// ... same as before ...
dragging = false; // initially the ball is not being dragged
}
void draw() {
// ... same as before ...
}
void mouseDragged() {
if (pointInCircle(mouseX, mouseY, x, y, diam / 2)) {
dragging = true;
dx = 0;
dy = 0;
x += mouseX - pmouseX;
y += mouseY - pmouseY;
}
}
void mouseReleased() {
if (dragging) {
dragging = false;
dx = mouseX - pmouseX;
dy = mouseY - pmouseY;
}
}
This fixes the problem: now you can only throw the ball when you are dragging it.
Questions¶
- Explain the difference between
mouseX
andpmouseX
. - When is
mouseReleased()
called?
Programming Questions¶
- Modify the ball-throwing program so that the ball stops moving if you right-click the mouse on the ball. Hint: Read about the mouseClicked() function and the mouseButton variable.
The Ball Throwing Program¶
float x, y; // center of the ball
float diam;
float dx, dy; // velocity of the ball
boolean dragging;
void setup() {
size(500, 500);
smooth();
x = 250;
y = 250;
diam = 60;
dx = 0;
dy = 0;
dragging = false;
}
void draw() {
background(255);
// draw the ball
noStroke();
fill(200, 0, 0);
ellipse(x, y, diam, diam);
// update the ball's position
x += dx;
y += dy;
// has ball hit the left edge?
if (x - diam / 2 < 0) {
x = diam / 2;
dx = -dx;
}
// has ball hit the right edge?
if (x + diam / 2 >= 500) {
x = 500 - diam / 2;
dx = -dx;
}
// has ball hit the top edge?
if (y - diam / 2 < 0) {
y = diam / 2;
dy = -dy;
}
// has ball hit the bottom edge?
if (y + diam / 2 >= 500) {
y = 500 - diam / 2;
dy = -dy;
}
}
void mouseDragged() {
if (pointInCircle(mouseX, mouseY, x, y, diam/2)) {
dragging = true;
dx = 0;
dy = 0;
x += mouseX - pmouseX;
y += mouseY - pmouseY;
}
}
void mouseReleased() {
if (dragging) {
dragging = false;
dx = mouseX - pmouseX;
dy = mouseY - pmouseY;
}
}
boolean pointInCircle(float x, float y, float a, float b, float r) {
if (dist(x, y, a, b) <= r) {
return true;
} else {
return false;
}
}