We are about to switch to a new forum software. Until then we have removed the registration on this forum.
Yes, that's not really a question, but that's still a common problem met by most newcomers to programming, showing their code in the forum.
Lot of newbies with Processing start by making the famous bouncing balls: a circle (or more) that travels in straight line, bouncing on the bounds of the sketch. That's a good starting point to show how to scale this kind of sketch.
The base code, with one ball, is shown in the How do I display a message for a few seconds? article, I won't repeat it here.
Now, say we want two balls. What most beginners do is to duplicate all the code for one ball to have two balls:
// First ball parameters
float posX1, posY1; // Position
float speedX1, speedY1; // Movement (linear)
int radius1; // Radius of the ball
color ballColor1; // And its color
// Second ball parameters
float posX2, posY2;
float speedX2, speedY2;
int radius2;
color ballColor2;
void setup()
{
size(600, 400);
smooth();
// Initialize the ball's data
posX1 = 120;
posY1 = 50;
speedX1 = -2;
speedY1 = 3;
radius1 = 24;
ballColor1 = #002277;
// Again for the second ball
posX2 = 220;
posY2 = 150;
speedX2 = 2;
speedY2 = -3;
radius2 = 32;
ballColor2 = #007722;
}
void draw()
{
// Erase the sketch area with some light color
background(#AAFFEE);
// Compute the new ball position
moveBall1();
// And display it
displayBall1();
// And again for the second ball
moveBall2();
displayBall2();
}
void moveBall1()
{
// Move by the amount determined by the speed
posX1 += speedX1;
// Check the horizontal position against the bounds of the sketch
if (posX1 < radius1 || posX1 > width - radius1)
{
// We went out of the area, we invert the h. speed (moving in the opposite direction)
// and put back the ball inside the area
speedX1 = -speedX1;
posX1 += speedX1;
}
// Idem for the vertical speed/position
posY1 += speedY1;
if (posY1 < radius1 || posY1 > height - radius1)
{
speedY1 = -speedY1;
posY1 += speedY1;
}
}
void displayBall1()
{
// Simple filled circle
noStroke();
fill(ballColor1);
ellipse(posX1, posY1, radius1 * 2, radius1 * 2);
}
void moveBall2()
{
posX2 += speedX2;
if (posX2 < radius2 || posX2 > width - radius2)
{
speedX2 = -speedX2;
posX2 += speedX2;
}
posY2 += speedY2;
if (posY2 < radius2 || posY2 > height - radius2)
{
speedY2 = -speedY2;
posY2 += speedY2;
}
}
void displayBall2()
{
noStroke();
fill(ballColor2);
ellipse(posX2, posY2, radius2 * 2, radius2 * 2);
}
Well, it works fine...
But when you read the code, that's twice as much code to understand, except that's the same code... And if you need to fix or change something, you have to do it twice.
Moreover, if you are asked to make 3 or more balls, you start to be in trouble... This doesn't scale well. At best, you can make one displayBall()
routine with parameters:
void displayBall(color ballColor, float posX, float posY, int radius)
{
noStroke();
fill(ballColor);
ellipse(posX, posY, radius * 2, radius * 2);
}
and call it with the parameters of each ball, but moveBall()
will have even more parameters; long lists of parameters are not very practical to handle.
And the repetitive long declarations at the start, and the initializations in setup() are not handy either.
At this point, some people have an idea, and ask how to generate variable numbers. Their idea is that since we have ballColor1
, ballColor2
, etc., why not access the variables like: ballColor + n
or "ballColor" + n
or some other creative syntax, where n
is the index.
But, in Java, this doesn't work. This can be done, by reflection, but it is used only in special cases, not for such common usage, so we won't see that here.
The solution is simply to use arrays. This frighten some newbies, but it is really quite simple to use. Where you have one variable, you make an array, and then functions can access the nth item just by being given a number:
// Ball parameters
// Positions
float[] posX;
float[] posY;
// Movements (linear)
float[] speedX;
float[] speedY;
// Radius of the balls
int[] radius;
// And the colors
color[] ballColor;
void setup()
{
size(600, 400);
smooth();
// Initialize the ball's data
posX[0] = 120;
posY[0] = 50;
speedX[0] = -2;
speedY[0] = 3;
radius[0] = 24;
ballColor[0] = #002277;
}
void draw()
{
// Erase the sketch area
background(#AAFFEE);
// Compute the new ball position
moveBall(0);
// And display it
displayBall(0);
}
void moveBall(int n)
{
// Move by the amount determined by the speed
posX[n] += speedX[n];
// Check the horizontal position against the bounds of the sketch
if (posX[n] < radius[n] || posX[n] > width - radius[n])
{
// We went out of the area, we invert the h. speed (moving in the opposite direction)
// and put back the ball inside the area
speedX[n] = -speedX[n];
posX[n] += speedX[n];
}
// Idem for the vertical speed/position
posY[n] += speedY[n];
if (posY[n] < radius[n] || posY[n] > height - radius[n])
{
speedY[n] = -speedY[n];
posY[n] += speedY[n];
}
}
void displayBall(int n)
{
// Simple filled circle
noStroke();
fill(ballColor[n]);
ellipse(posX[n], posY[n], radius[n] * 2, radius[n] * 2);
}
Except this won't work, as I deliberately made an error. If you run this code, you will have a NullPointerException.
Why? The arrays have been declared with the []
notation, accessed with the [0]
or [n]
notation, but we need to declare how many items there will be in the arrays: in Java, arrays won't grow automatically, unlike some other programming languages.
The arrays can be initialized like this:
// Ball parameters
// Positions
float[] posX = new float[10];
float[] posY = new float[10];
// Movements (linear)
float[] speedX = new float[10];
float[] speedY = new float[10];
// Radius of the balls
int[] radius = new int[10];
// And the colors
color[] ballColor = new color[10];
and then the sketch will work.
Note that all the arrays must have the same size, each index corresponding to one ball object. So, it is better to define a constant instead of repeating the same number everywhere. The advantages: it is less a "magical" number without meaning. You have only one place to change it, if you want more, or less, balls. If you search/replace 10 with another number, you risk to alter another 10
constant with nothing to do with the array size.
So the code will look now like:
// Ball parameters
final int BALL_NB = 10; // final just indicate this value cannot be changed in the program
// Positions
float[] posX = new float[BALL_NB];
float[] posY = new float[BALL_NB];
// Movements (linear)
float[] speedX = new float[BALL_NB];
float[] speedY = new float[BALL_NB];
// Radius of the balls
int[] radius = new int[BALL_NB];
// And the colors
color[] ballColor = new color[BALL_NB];
Now, if you want another ball, you have to change setup() and draw():
void setup()
{
size(600, 400);
smooth();
// Initialize the ball's data
posX[0] = 120;
posY[0] = 50;
speedX[0] = -2;
speedY[0] = 3;
radius[0] = 24;
ballColor[0] = #002277;
// Initialize the other ball's data
posX[1] = 220;
posY[1] = 150;
speedX[1] = 2;
speedY[1] = -3;
radius[1] = 32;
ballColor[1] = #007722;
}
void draw()
{
// Erase the sketch area
background(#AAFFEE);
// Compute the new ball position
moveBall(0);
// And display it
displayBall(0);
// Again
moveBall(1);
displayBall(1);
}
OK, obviously, the loop in draw()
can use a for loop:
void draw()
{
// Erase the sketch area
background(#AAFFEE);
for (int i = 0; i < BALL_NB; i++)
{
// Compute the new ball position
moveBall(i);
// And display it
displayBall(i);
}
}
but we haven't filled yet all the slots of the arrays. It still works, because arrays are filled by default with the default value of their type: 0 for ints and floats, false for booleans, etc. So here we are just displaying balls of size 0...
Notice the loop: array indexes start at 0, and end at the array size minus 1, hence the <
condition in the loop.
Initializing the arrays like we do above in setup()
isn't very practical: it is quite verbose. We can use a loop and initialize the values with random numbers:
void setup()
{
size(600, 400);
smooth();
// Initialize the balls' data
for (int i = 0; i < BALL_NB; i++)
{
radius[i] = int(random(10, 50));
posX[i] = random(radius[i], width - radius[i]);
posY[i] = random(radius[i], height - radius[i]);
speedX[i] = random(-10, 10);
speedY[i] = random(-10, 10);
ballColor[i] = color(0, random(100, 255), random(100, 255));
}
}
Now, we might want to have a precise color for each ball, and perhaps a precise radius, while keeping pos and speed random. Java has a shortcut notation to declare an array while initializing its values.
// Ball parameters
final int BALL_NB = 5;
// Positions
float[] posX = new float[BALL_NB];
float[] posY = new float[BALL_NB];
// Movements (linear)
float[] speedX = new float[BALL_NB];
float[] speedY = new float[BALL_NB];
// Radius of the balls
int[] radius = { 8, 16, 24, 32, 48 };
// And the colors
color[] ballColor = { #DDEE55, #AA44EE, #22BBAA, #0022FF, #00FF22 };
void setup()
{
size(600, 400);
smooth();
// Initialize the balls' data
for (int i = 0; i < BALL_NB; i++)
{
posX[i] = random(radius[i], width - radius[i]);
posY[i] = random(radius[i], height - radius[i]);
speedX[i] = random(-7, 7);
speedY[i] = random(-7, 7);
}
}
Note that this notation is only used at the declaration site. If, for any reason, you need to use this notation after the array have been initialized, you have to use a slightly more convoluted notation:
ballColor = new color[] { #DDEE55, #AA44EE, #22BBAA, #0022FF, #00FF22 };
Having all these "parallel" arrays isn't very practical, there isn't much cohesion, we still have repetitive code (the array declarations), information is a bit dispersed.
In the next article, From several arrays to classes, we will show that using classes is an elegant way to address these issues.