Handling the Keyboard¶
Processing has built-in support for reading key-presses and key-releases from the keyboard. So in this note we’ll make a program that lets the user move a box around the screen using both the WASD keys and the arrow keys.
A Box Class¶
First, lets create a class called Box
that will draw the sprite we will be
moving around the screen:
class Sprite {
float x;
float y;
float dx;
float dy;
void update() {
x += dx;
y += dy;
}
}
class Box extends Sprite {
String state; // "square", "circle"
float radius;
// constructor
Box() {
x = width / 2;
y = height / 2;
dx = 0;
dy = 0;
state = "circle";
radius = 50;
}
void render() {
if (state.equals("square")) {
rectMode(CENTER);
noStroke();
fill(255, 0, 0);
rect(x, y, 2 * radius, 2 * radius);
} else if (state.equals("circle")) {
noStroke();
fill(255, 0, 0);
ellipse(x, y, 2 * radius, 2 * radius);
}
}
void update() {
super.update();
x = constrain(x, radius, width - radius);
y = constrain(y, radius, height - radius);
}
void halt() {
dx = 0;
dy = 0;
}
void flipState() {
if (state.equals("square")) {
state = "circle";
} else if (state.equals("circle")) {
state = "square";
}
}
void increaseSize() {
radius += 5;
radius = constrain(radius, 5, 100);
}
void decreaseSize() {
radius += -5;
radius = constrain(radius, 5, 100);
}
} // class Box
Box
includes these functions:
Box
, a default constructor, that initializes the variables with the values we want them to have. This constructor is called whenever we create a newBox
object, e.g. in statements like this:Box b = new Box(); // Box constructor called
render
, for drawing theBox
, either as a circle or a square (depending upon the value ofstate
). Note that the square is drawn usingrectMode(CENTER)
so that it appears in the same position as the circle.update
, which first calls theupdate()
function in theSprite
class, and then usesconstrain
to make sure the sprite is always on the screen.halt
, for stopping the sprite.flipState
, for changing it from a circle to square, and vice-versa.increaseSize()
anddecreaseSize()
, for changing the size of the sprite. Note how these functions useconstrain
to keep the sprite from getting too small or too big.
Here’s our starting program, which draws a non-moving square in the middle of the screen:
Box box;
void setup() {
size(500, 500);
box = new Box();
}
void draw() {
background(255);
box.render();
box.update();
}
Key Presses and Key Releases¶
The keyboard is complex piece of hardware. Not only does it have many different keys, but it also allows more than one key to be pressed at once, allowing you type things like Q, or shift-Q, or alt-Q, or ctrl-Q, etc.
An individual key-press consists of two distinct actions: pressing the key, and then releasing the key. Processing treats these as two different events that you can handle in whatever way you like using these functions:
void keyPressed() {
// automatically called when any key is pressed
}
void keyReleased() {
// automatically called when any key is released
}
Moving the Sprite¶
Lets move the sprite by pressing the WASD keys, where W is up, S is down, A is left, and D is right. Lets agree that we only want the sprite to move while the corresponding key is being pressed:
void keyPressed() {
if (key == 'w' || key == 'W') { // up
box.dx = 0;
box.dy = -2;
} else if (key == 's' || key == 'S') { // down
box.dx = 0;
box.dy = 2;
} else if (key == 'a' || key == 'A') { // left
box.dx = -2;
box.dy = 0;
} else if (key == 'd' || key == 'D') { // right
box.dx = 2;
box.dy = 0;
}
}
void keyReleased() {
if (key == 'w' || key == 'W') { // up
box.halt();
} else if (key == 's' || key == 'S') { // down
box.halt();
} else if (key == 'a' || key == 'A') { // left
box.halt();
} else if (key == 'd' || key == 'D') { // right
box.halt();
}
}
Notice a couple of important things here:
- We check for both the lowercase and uppercase versions of the letter. So if
the CAPS-LOCK key happens to be on the sprite will still move. Recall that
||
means “or”. - When, say, the W key is pressed down, the object starts moving. It doesn’t
stop moving until the the W key is released. Thus, we need to use both the
keyPressed
andkeyReleased
functions. - The special Processing variable
key
is of typechar
, and things like's'
and'D'
are examples ofchar
literals. Notice how they are wrapped in single-quotes, and not double-quotes (onlyString
literals are wrapped in double quotes). Also, notice that==
(not.equals
, which is used to compare strings) is used to test if two characters are the same.
Handling Arrow Keys¶
Processing treats the arrow-keys as specially encoded keys that are handled slightly differently from letter-keys (like W, A, S, and D). You need to check two variables to see if an arrow key is pressed, e.g.:
if (key == CODED && keyCode == UP) {
println("up arrow pressed");
}
When you press and arrow key, Processing assigns key
the special value
CODED
, and then assigns the variable keyCode
the value of the key that
was pressed.
So, to handle arrow keys, we re-write keyPressed
and keyReleased
like
this:
void keyPressed() {
if ((key == CODED && keyCode == UP)
|| key == 'w' || key == 'W') { // up
box.dx = 0;
box.dy = -2;
} else if ((key == CODED && keyCode == DOWN)
|| key == 's' || key == 'S') { // down
box.dx = 0;
box.dy = 2;
} else if ((key == CODED && keyCode == LEFT)
|| key == 'a' || key == 'A') { // left
box.dx = -2;
box.dy = 0;
} else if ((key == CODED && keyCode == RIGHT)
|| key == 'd' || key == 'D') { // right
box.dx = 2;
box.dy = 0;
}
}
void keyReleased() {
if ((key == CODED && keyCode == UP)
|| key == 'w' || key == 'W') { // up
box.halt();
} else if ((key == CODED && keyCode == DOWN)
|| key == 's' || key == 'S') { // down
box.halt();
} else if ((key == CODED && keyCode == LEFT)
|| key == 'a' || key == 'A') { // left
box.halt();
} else if ((key == CODED && keyCode == RIGHT)
|| key == 'd' || key == 'D') { // right
box.halt();
}
}
The if-statement expressions are starting to get long and messy, and so we split them into lines.
With this change the user can now move the sprite using either the WASD key, or the arrow keys — whichever they prefer.
Changing the Sprite¶
Finally, lets allow the user to change the sprite from a circle to a square
(or vice versa) by pressing the space bar, and to increase, or decrease, the
size of the sprite using -
and +
. Only the keyPressed
function
needs to be changed:
void keyPressed() {
if (key == '-' || key == '_') {
box.decreaseSize();
} else if (key == '+') {
box.increaseSize();
} else if (key == ' ') {
box.flipState();
} else if ((key == CODED && keyCode == UP)
|| key == 'w' || key == 'W') { // up
box.dx = 0;
box.dy = -2;
} else if ((key == CODED && keyCode == DOWN)
|| key == 's' || key == 'S') { // down
box.dx = 0;
box.dy = 2;
} else if ((key == CODED && keyCode == LEFT)
|| key == 'a' || key == 'A') { // left
box.dx = -2;
box.dy = 0;
} else if ((key == CODED && keyCode == RIGHT)
|| key == 'd' || key == 'D') { // right
box.dx = 2;
box.dy = 0;
}
}
Notice that _
can also be used to decrease the size of the sprite. This
makes it easy to hold down the shift key and alternate between pressing the
+
and -
keys.
Questions¶
- Explain in brief, clear English the difference between a key-press and a key-release.
- Why does the program check for both the lowercase and uppercase versions of keys?
- What is the data type of the pre-defined Processing variable
key
? - Describe, in English, how the sprite moves if you comment-out the entire
keyReleased
function.
Programming Questions¶
Modify the program so that when the user presses one of the digit keys
'0'
,'1'
,'2'
, ...,'9'
, the speed of movement is set to be that number. For example, pressing'4'
will cause the sprite to move 4 pixels per frame for the rest of the program (or until the user presses another digit key). If the user presses'0'
, the sprite will not move at all (i.e. it will move at 0 pixels per frame).To aid with this, write a helper function called
isDigit
with this structure:boolean isDigit(char c) { // ... }
It should return
true
just whenc
is one of'0'
,'1'
,'2'
, ..., or'9'
, andfalse
otherwise.Modify the program to allow diagonal movement. For instance, you could use
'q'
,'e'
,'z'
, and'c'
for the diagonal keys.Modify the final program so that when the sprite goes off an edge, it wraps-around to the opposite edge and continues moving with the same velocity.
Final Program¶
class Sprite {
float x;
float y;
float dx;
float dy;
void update() {
x += dx;
y += dy;
}
}
class Box extends Sprite {
String state; // "square", "circle"
float radius;
// constructor
Box() {
x = width / 2;
y = height / 2;
dx = 0;
dy = 0;
state = "circle";
radius = 50;
}
void render() {
if (state.equals("square")) {
rectMode(CENTER);
noStroke();
fill(255, 0, 0);
rect(x, y, 2 * radius, 2 * radius);
} else if (state.equals("circle")) {
noStroke();
fill(255, 0, 0);
ellipse(x, y, 2 * radius, 2 * radius);
}
}
void update() {
super.update();
x = constrain(x, radius, width - radius);
y = constrain(y, radius, height - radius);
}
void halt() {
dx = 0;
dy = 0;
}
void flipState() {
if (state.equals("square")) {
state = "circle";
} else if (state.equals("circle")) {
state = "square";
}
}
void increaseSize() {
radius += 5;
radius = constrain(radius, 5, 100);
}
void decreaseSize() {
radius += -5;
radius = constrain(radius, 5, 100);
}
} // class Box
Box box;
void setup() {
size(500, 500);
box = new Box();
}
void draw() {
background(255);
box.render();
box.update();
}
void keyPressed() {
if (key == '-' || key == '_') {
box.decreaseSize();
} else if (key == '+') {
box.increaseSize();
} else if (key == ' ') {
box.flipState();
} else if ((key == CODED && keyCode == UP) || key == 'w' || key == 'W') { // up
box.dx = 0;
box.dy = -2;
} else if ((key == CODED && keyCode == DOWN) || key == 's' || key == 'S') { // down
box.dx = 0;
box.dy = 2;
} else if ((key == CODED && keyCode == LEFT) || key == 'a' || key == 'A') { // left
box.dx = -2;
box.dy = 0;
} else if ((key == CODED && keyCode == RIGHT) || key == 'd' || key == 'D') { // right
box.dx = 2;
box.dy = 0;
}
}
void keyReleased() {
if ((key == CODED && keyCode == UP) || key == 'w' || key == 'W') { // up
box.halt();
} else if ((key == CODED && keyCode == DOWN) || key == 's' || key == 'S') { // down
box.halt();
} else if ((key == CODED && keyCode == LEFT) || key == 'a' || key == 'A') { // left
box.halt();
} else if ((key == CODED && keyCode == RIGHT) || key == 'd' || key == 'D') { // right
box.halt();
}
}