Tutorial Tilt game Processing: translate, sin, cos, abs, rotate, keyPressed, box, sphere, millis, indexOf, substring, min

This sketch implements simulation of a board that can be tilted along two axes. A ball, represented as a sphere, rolls when the board is not flat. When it is determined that the ball is off the board, it falls down. The player uses the arrow keys to tilt the board. The game keeps track of the time in motion. The game will re-set after a certain amount of time has elapsed with no action by the player.

The opening display screen is:

Next is a screen shot of the ball just off the board and about to fall down:

In this last screen shot, the board has been tilted down in the back.

The critical features of this application were:

  • capturing key strokes to detect pressing of the arrow keys
  • displaying the board at the appropriate orientation (angles) and the ball on the board
  • simulating the motion of the ball: this included calculating the angles and also providing for acceleration of the ball rolling on the board and the ball falling down from the board. Falling straight down from the viewpoint of the viewer required extra calculation.
  • keeping track of time and re-setting the game to the initial state
  • displaying the elapsed time and formatting this to be just 2 decimal places.

Coordinate system

The coordinate system of Processing 3D uses the x y and z axes. The labels and the arrow heads indicate the positive position.

The box that represents the board is positioned and drawn by translation and then by a specified amount of rotation on the z axis, tilting the board on the right as shown in the second screen shot or the opposite way (the left side), and rotation on the x axis, which pushes the back down or the front down. I made the decision to tilt the board with the front slightly down for the original setting because it made the 3D aspect more obvious. This was a rotation of the X axis.

Capturing Key strokes

Processing provides the keyPressed function that is invoked whenever a keyboard key is pressed. The keyCode variable holds the number and the LEFT, RIGHT, UP and DOWN contain the values for the corresponding arrow keys. This makes 'capturing' a key press, that is, detecting that the player has pressed a specific key easy to do.

The question then was how to associate the arrows with the tilting of the board. I decided that pressing the right key should indicate pressing down on the right side of the board. This corresponds to increasing the rotation around the z axis. Similarly, clicking the left key should cause the board to tilt to the left. The up and down key correspondence is less obvious. I made clicking on the down arrow be like someone pushed down on the front of the board and clicking on the up arrow would be like someone pushing down on the back of the board, the board furthest away. The third screen shot shown earlier demonstrates the effects of pressing the up arrow 3 times. This leads to the next topic.

Displaying a board rotated along 2 dimensions

As you know from reading other tutorials and/or other experience with Processing, the draw function is invoked at the rate set by frameRate. In this sketch, I erase the whole display screen by calling on background(255). I then display text (see later). The next step is to translate the coordinates from the point projected to the upper left corner to the middle of the display screen: translate(width/2,height/2). The next hunk of code in draw relates to the check and resetting after a pause, also to be explained later. THEN the code rotates along the Z axis and rotates along the X axis the values of the variables zrot and xrot. These are the values modified by the key presses. After the translation and rotations, the code draws a box, the representation of the board. The translate command is called again with the variables that define the position of the ball and the sphere command displays the sphere.

Translations and rotations are cumulative within one call of the draw function so the translation positioning the ball is done after, you can say in terms of, the rotations and translation the defined the box. However, at the next invocation of the draw function, things start all over again.

Simulating motion of the ball

[You can review the code and/or the tutorial for the cannonball sketch to see another example of simulation of motion, in that case, the ballistics action of a ball with constant horizontal velocity and changing vertical velocity.]

The draw function calls the moveball function, which calculates the offsets in each of the 3 directions: xd, zd, and yd and then uses them to modify the variables xpos, zpos, and ypos. I write them in that order because while the ball is on the board, the y position does not change. The xd and zd values are calculated by taking the sine of the change in each angle from the original angle. This is because it is only the downward part that provides any acceleration. The bigger the change the larger the speed. The offset calculation also involves averaging the value at the start of the interval with the calculated value, multiplied by (1+g).

zd = .5*(speed*sin(origxrot-xrot)+zdold)*(1+g);

xd = .5*(speed*sin(zrot-origzrot)+xdold)*(1+g);

Putting this another way: the ball accelerates rolling down the board. If the board is restored to a level position (and 'level' applies to each plane separately), the ball will gradually come to a stop.

The moveball function also checks if the ball has gone past the edges of the box. For example, see the code:

if (xpos>(rightwall+sr)) {

xd = 0;

zd = 0;

yda = 1;

}

This checks if the whole ball is beyond the right wall and prevents it looking as if the ball goes through the edges of the board.

When this occurs, the xd and yd values are each set to 0 and a variable named yda is set to 1. Note: I originally thought that this was all I needed to do to make the ball drop. However, this was incorrect for two reasons. One is that in rotated coordinate system, y is not strictly vertical, so the ball was not falling straight down. I could have used pushMatrix and popMatrix to restore the coordinate system before the rotations, but that would have required calculations to determine the proper xpos , ypos and zpos. Instead, I calculated the yd, xd, and zd values in the new coordinate system that would achieve the downward movement.

The other issue was that I wanted the ball to accelerate when falling. This was accomplished by adding g to yda each iteration. This code is executed in the moveball function when the yda value is determined to be greater than 0.

Keeping track of time

There are two calculations involving time. One is checking if a sufficiently long time has passed with no player action. The millis function returns the number of milliseconds since the sketch was invoked. The code uses this function to update the last variable whenever keyPressed is invoked. The millis function is used again to see if millis()=last is greater than the specified pause time held in the variable rest. This is done in the draw function.

The other calculation involving time is to increase the variable elapsed whenever the ball is rolling on the board. This is done by checking if the absolute value of xd plus the absolute value of zd is greater than zero. Because these are floating point numbers and can be very close to zero, but not zero, I make the comparison to .1. When this is the case, the code increments elapsed by tpf. This variable, actually a constant, is set at the time for one frame iteration. The check on the absolute values also is the signal to modify xpos and zpos and also check for hitting the walls.

Displaying elapsed time

The messages to the player include 2 fixed messages plus the message in the middle of the screen that includes the elapsed time. Like all text messages, this requires declaring and then obtaining a font value, using textFont and then using text. For this application, I used the function loadFont after using Tools/Create font…. to make the font part of the sketch itself (and the applet). See the other tutorial or the Processing Help/Reference for details.

I decided that I wanted to show a floating point number with only two decimal points. My nice function did this job. The nice function first uses str to convert the number to a string and then finds the position of the decimal point, using indexOf. The next step is to take the minimum of this index value plus 3 and the length of the string. The substring method is used to get just this much of the string, effectively truncating the string past the 2 decimal points.

Implementation

The global variables that serve as constants are:

float speed = 2;

float g = .75; //arbitrary--looks good

float leftwall = -200;

float rightwall = 200;

float backwall = -100;

float frontwall = 100;

float yheight = 20;

float rest = 15000; //used to reset game after pause with no arrow keys

float fr = 12; //frame rate

float tpf = 1/fr; //used to calculate time in motion

float sr = 10; //sphere radius

float rd = PI/12;

The global variables that actually vary are:

tgame.

The sketch consists of definitions for the recognized Processing functions: setup, draw, and keyPressed and the programmer-created functions: moveball and nice. The draw function calls both moveball and nice.

void setup() {

size(1000,600,P3D);

frameRate(fr);

last = millis();

myfont = loadFont("Pedro-Regular-28.vlw"); //font has been created and added to data folder

textFont(myfont);

}

void draw() {

background(255);

fill(250,0,250);

text(" Use arrows to tilt board.",30,70);

text(" Time in motion: " + nice(elapsed)+" secs.",width/3,70);

text(" Game will re-set after a pause.",2*width/3,70);

translate(width/2,height/2);

if ((millis()-last)>rest) {

xrot=origxrot;

zrot=origzrot;

xpos = 0;

zpos = 0;

ypos = (-yheight);

xd = 0;

zd = 0;

yd = 0;

yda = 0;

elapsed = 0;

}

rotateZ(zrot);

rotateX(xrot);

fill(100,0,200);

stroke(0);

box(rightwall-leftwall, yheight, frontwall-backwall);

moveball();

translate(xpos, ypos, zpos);

stroke(255,0,0);

sphere(sr);

}

void moveball() {

//determines x and z deltas from angle of board

//accelerates even if same angle

zd = .5*(speed*sin(origxrot-xrot)+zdold)*(1+g);

xd = .5*(speed*sin(zrot-origzrot)+xdold)*(1+g);

//println(xd);

zdold = zd;

xdold = xd;

if (yda>0) { //if moving down, continue and accelerate

yda+=g;

yd = yda*cos(zrot)*cos(xrot);

xd = yda*sin(zrot);

zd = -yda*cos(zrot)*sin(xrot);

ypos +=yd;

xpos +=xd;

zpos +=zd;

}

else

if ((abs(xd)+abs(zd))>0) { //if xd and/or yd non-zero

// change in position

elapsed +=tpf;

xpos +=xd;

zpos +=zd;

//check for falling off board

if (xpos>(rightwall+sr)) {

xd = 0;

zd = 0;

yda = 1;

}

if (xpos<(leftwall-sr)) {

xd = 0;

zd = 0;

yda = 1;

}

if (zpos>(frontwall+sr)) {

xd = 0;

zd = 0;

yda = 1;

}

if (zpos<(backwall-sr)) {

xd = 0;

zd = 0;

yda = 1;

}

}

}

void keyPressed() {

if (keyCode==LEFT) {

zrot -= rd;

}

if (keyCode==RIGHT) {

zrot += rd;

}

if (keyCode==UP) {

xrot += rd;

}

if (keyCode==DOWN) {

xrot -= rd;

}

last = millis();

}

String nice(float f) { //returns string with at most 2 characters past decimal point

String s = str(f);

int p = s.indexOf(".");

int sl = min(p+3,s.length());

return s.substring(0,sl);

}

Disclaimer: all simulations involve approximations. This simulation is suggestive of a ball rolling on a board. Similarly, Processing is new to me and this may not be the best way to use its features. Comments and feedback are most welcome.