(Extra) Making a Car¶
The Sprite
class we’ve been writing in the last few lessons can be
used as the basis for creating more complex animated objects. In this
note we’ll see how to create an animated car consisting of a number of
individual sprites.
The Car Class¶
Lets make a side-view of a 2-dimensional car. The car will consist of three sprites: a body, a front wheel, and a back wheel. Right away this lets us write some code:
class Car {
Sprite body;
Sprite frontWheel;
Sprite backWheel;
Car() {
body = new Sprite("blueCarBody_small.png");
frontWheel = new Sprite("carWheel_small.png");
backWheel = new Sprite("carWheel_small.png");
}
// ...
} // class Car
Two image files are used here: blueCarBody_small.png
, and carWheel_small.png
. These ones were created using a
drawing program, and slightly edited to make them a little smaller. Of
course, you can replace these files with whatever graphics you like.
We also need to keep track of the position of the (x, y) position of the entire car:
class Car {
float x, y;
// ...
} // class Car
Rendering and Updating¶
Rendering and updating a Car
object requires rendering and
updating its three constituent sprites. The update()
function is
straightforward:
void update() {
body.update();
frontWheel.update();
backWheel.update();
}
Rendering is a little bit more involved because we need to specify the
location of the wheels on the car. The best way to do this is to use
the translate
function because it applies to all the sprites:
void render() {
pushMatrix();
translate(x, y); // move the origin here
body.render();
translate(50, 50);
frontWheel.render();
translate(-75, 0);
backWheel.render();
popMatrix();
}
Note the following:
The code in
render()
is wrapped in calls topushMatrix()
andpopMatrix()
because it changes the entire coordinate system whentranslate
is called. This ensures that the coordinate system is always reset to the way it was before the call torender()
.After the body is rendered (by calling
body.render()
), the wheels are drawn. The way to think about this is to imagine that the car has been placed at (0, 0), and then to adjust the position of the wheels relative to (0, 0). For example, before renderingfrontWheel
we calltranslate(50, 50)
, which moves the wheel 50 pixels to the right and 50 pixels down.The values for the wheel positions were chosen experimentally.
Visibility¶
Our sprites are either visible or invisible, and so we can make a
Car
object invisible by setting the body
, frontWheel
, and
backWheel
to be invisible. We add two functions to do this:
class Car {
// ...
void setVisible() {
body.visible = true;
frontWheel.visible = true;
backWheel.visible = true;
}
void setInvisible() {
body.visible = false;
frontWheel.visible = false;
backWheel.visible = false;
}
// ...
} // class Car
These functions just set the visibility of the constituent objects.
Velocity¶
We also want to set the cars speed. The body and wheels should all
move at the same speed, and so we will add a function called
setVelocity
:
class Car {
// ...
void setVelocity(float dx, float dy) {
body.dx = dx;
body.dy = dy;
frontWheel.dx = dx;
frontWheel.dy = dy;
backWheel.dx = dx;
backWheel.dy = dy;
}
// ...
} // class Car
The wheels of the car should rotate on their own, and so we add
setWheelSpeed
:
class Car {
// ...
void setWheelSpinRateInDegrees(float rate) {
frontWheel.spinRate = radians(rate);
backWheel.spinRate = radians(rate);
}
// ...
} // class Car
The input parameter to setWheelSpinRateInDegrees
is in degrees
simply because it is assumed the user of a Car
object would prefer
degrees instead of radians.
The long function name is to take make it absolutely clear that the
rate
is in degrees (and not radians).
Scale¶
Finally, lets add a variable to change the size of the car:
class Car {
// ...
float imageScale;
Car() {
// ...
imageScale = 1;
}
void render() {
pushMatrix();
translate(x, y);
scale(imageScale);
// ...
popMatrix();
}
// ...
} // class Car
By default, imageScale
is set to 1, meaning that it will be drawn
at its natural size. Setting imageScale
to, say, 2.5, will draw
the image at two-and-a-half times it natural size.
Like translate
and rotate
, the scale
function applies to
the entire coordinate system: it shrinks/expands the distance between
every point of the coordinate system by the same given amount. Thus
every drawing command after a call to scale
will be at the new
scale. For our car this means that the wheels are drawn at the correct
location on scaled-down or scaled-up images.
Using the Car Class¶
Now that our Car
class is written, it is fun and easy to use. For
example:
Car car;
void setup() {
size(500, 500);
car = new Car();
car.setVelocity(1, 0);
car.frontWheel.angle = radians(40); // make the wheels look different
car.setWheelSpinRateInDegrees(1);
car.y = 200;
car.imageScale = 0.5;
car.setVisible();
}
void draw() {
background(255);
car.render();
car.update();
}
Notice how simple the code is: most of our work is in creating and
initializing the Car
object. It’s not hard to imagine that
someone, say, an artist or designer, could learn to use code like this
without having to become a Processing expert.
Random Cars¶
Lets create a demo program that has multiple cars moving across the screen at different speeds. The position, velocity, and size of the cars will be chosen at random.
The first thing we need is an ArrayList
to store our Car
objects:
final int NUM_CARS = 10;
ArrayList<Car> cars;
void setup() {
size(500, 500);
cars = new ArrayList<Car>();
for(int i = 0; i < cars.size(); ++i) {
// ... initialize cars...
}
}
void draw() {
background(255);
for(Car c : cars) {
c.render();
c.update();
}
}
Now we want to create some cars with a random position, size, and
velocity. The best way to do that is to write a function that returns
new Car
objects that have been initialized the way we want:
Car makeRandomCar() {
Car c = new Car();
c.x = -10;
c.y = random(450);
c.imageScale = random(0.25, 1);
float dx = random(0.5, 2);
c.setVelocity(dx, 0);
c.setWheelSpinRateInDegrees(dx);
c.setVisible();
return c;
}
Note the following:
- The name of this function is
makeRandomCar
, and it returns a new object of typeCar
. It doesn’t take any input. - The statement
random(x, y)
returns a random number betweenx
andy
. The particular values used forrandom
in this function were chosen experimentally.
Using makeRandomCar
is easy; we just call inside the loop used to
initialize the car
array:
void setup() {
size(500, 500);
cars = new ArrayList<Car>();
for(int i = 0; i < cars.size(); ++i) {
randCar = makeRandomCar();
cars.add(randCar);
}
}
void draw() {
background(255);
for (Car c : cars) {
c.render();
c.update();
}
}
Programming Questions¶
Add a
void
function calledhonk()
to theCar
class that, when called, will make the car’s horn honk, e.g.:Car c; c = new Car(); c.honk(); // makes a honking sound
Write a program that makes a car drive across the screen and explode when it hits the right edge. Use the
Explosion
class to do this.
The Car Class¶
class Car {
float x, y; // position of the entire car
float imageScale;
Sprite body;
Sprite frontWheel;
Sprite backWheel;
Car() {
body = new Sprite("blueCarBody_small.png");
frontWheel = new Sprite("carWheel_small.png");
backWheel = new Sprite("carWheel_small.png");
imageScale = 1;
}
void update() {
body.update();
frontWheel.update();
backWheel.update();
}
void render() {
pushMatrix();
translate(x, y); // move the origin here
scale(imageScale);
body.render();
translate(50, 50);
frontWheel.render();
translate(-75, 0);
backWheel.render();
popMatrix();
}
void setVisible() {
body.visible = true;
frontWheel.visible = true;
backWheel.visible = true;
}
void setInvisible() {
body.visible = false;
frontWheel.visible = false;
backWheel.visible = false;
}
void setVelocity(float dx, float dy) {
body.dx = dx;
body.dy = dy;
frontWheel.dx = dx;
frontWheel.dy = dy;
backWheel.dx = dx;
backWheel.dy = dy;
}
void setWheelSpinRateInDegrees(float rate) {
frontWheel.spinRate = radians(rate);
backWheel.spinRate = radians(rate);
}
} // class Car