Multiple Screens

Most programs consist of multiple screens. For example, video games usually have, at least, an introductory screen, a play screen, and a “game over” screen. In this note we’ll see a general-purpose way to handle different kinds of screens in the same program.

Handling Multiple Screens

The essential trick we’re going to use for handling multiple screens is to use a variable called screen that keeps track of what the current screen is:

String screen;

This is sometimes known as a state variable, because indicates (part of) the current state of the program.

Any particular program will a list of screens. For instance, lets imagine a game that has an intro screen, a play screen (where the main game is played), and a game-over screen displayed at the end. We structure the program like this:

String screen;
// ... other variables ...

void setup() {
        size(500, 500);

        screen = "intro";  // initial screen

        // ...
}

void draw() {
        if (screen.equals("intro")) {
                // ... code for drawing the intro screen ...
        } else if (screen.equals("playing")) {
                // ... code for drawing the main game screen ...
        } else if (screen.equals("gameover")) {
                // ... code for drawing the game over screen ...
        } else {
                println("Error: " + screen + " is not a valid screen");
        }
}

void mouseClicked() {
        if (screen.equals("intro")) {
                // ... code for handling mouse clicks on the intro screen ...
        } else if (screen.equals("playing")) {
                // ... code for handling mouse clicks on the playing screen ...
        } else if (screen.equals("gameover")) {
           // ... code for handling mouse clicks on the game-over screen ...
        } else {
           println("Error: " + screen + " is not a valid screen");
        }
}

Now that we have more than one screen, every time we call functions like draw and mouseClicked we need to first determine what screen we are on. Once we know the value of screen, we can then execute the appropriate code.

Notice that we use .equals (and not ==!) to test if two strings are the same. Case matters when you .equals, and so "intro" and "Intro" will be considered different.

Note

In practice, making screen a String variable can cause performance problems if you have a lot of different screens. Since draw() is called 30-60 times a second, constantly having to check the value of screen can cause slow-downs. So a couple of other approaches are often used:

  • Make screen of type int instead of String. Each screen has a code number, e.g. the intro screen could be 1, the playing screen 2, and the game-over screen 3. This speeds of the checking done by the if-else-if statements, but it now forces you, the programmer, to remember what each code number means. In big programs, this is not always easy.
  • Use user-defined enumeration variables. These combine the performance of int variables with the readability of Strings. It also has the nice feature that it enables the compiler to catch spelling errors in the names of screens. While it does introduce new syntax, but it is probably the best solution in general.

Example: Three Simple Screens

Now lets use the template program above to implement a simple multi-screen program that simply displays the screen name on each screen, and then changes screens when you click the mouse:

String screen;

PFont font;

void setup() {
  size(500, 500);

  screen = "intro";  // initial screen

  font = loadFont("AndaleMono-48.vlw");
  textFont(font);
}

void draw() {
  if (screen.equals("intro")) {
    background(100);
    fill(255, 0, 0);
    text("Intro screen", 25, 50);
  }
  else if (screen.equals("playing")) {
    background(200);
    fill(0, 255, 0);
    text("Playing screen", 25, 50);
  }
  else if (screen.equals("gameover")) {
    background(255);
    fill(0, 0, 255);
    text("Game over screen", 25, 50);
  }
  else {
    println("Error: " + screen + " is not a valid screen");
  }
}

void mouseClicked() {
  if (screen.equals("intro")) {
    screen = "playing";
  }
  else if (screen.equals("playing")) {
    screen = "gameover";
  }
  else if (screen.equals("gameover")) {
    // do nothing
  }
  else {
    println("Error: " + screen + " is not a valid screen");
  }
}

When you run this program it first displays “Intro screen”. That’s because screen is initialized to the value "intro" in setup, and so the big if-else-if statement in draw selects the code to print “Intro screen”.

Whenever you click a mouse button, mouseClicked is automatically called. The if-else-if statement within in it then selects the code that should be run for the current screen. For example, if you click the mouse button anywhere on the intro screen, then screen = "playing" is called, which causes the screen become the main playing screen. Thus, the next time draw is called, a new message is printed.

Individual Draw Functions

Now that we have multiple screens, the draw() can get very long. The problem with long functions is that they can be quite hard to read, and so hard to understand and modify. So a good thing to do is to create one drawing function for each screen in our program:

String screen;

PFont font;

void setup() {
  size(500, 500);

  screen = "intro";  // initial screen

  font = loadFont("AndaleMono-48.vlw");
  textFont(font);
}

void draw() {
  if (screen.equals("intro")) {
    draw_intro();
  }
  else if (screen.equals("playing")) {
    draw_playing();
  }
  else if (screen.equals("gameover")) {
    draw_gameover();
  }
  else {
    println("Error: " + screen + " is not a valid screen");
  }
}

void draw_intro() {
  background(100);
  fill(255, 0, 0);
  text("Intro screen", 25, 50);
}

void draw_playing() {
  background(200);
  fill(0, 255, 0);
  text("Playing screen", 25, 50);
}

void draw_gameover() {
  background(255);
  fill(0, 0, 255);
  text("Game over screen", 25, 50);
}

void mouseClicked() {
  if (screen.equals("intro")) {
    screen = "playing";
  }
  else if (screen.equals("playing")) {
    screen = "gameover";
  }
  else if (screen.equals("gameover")) {
    // do nothing
  }
  else {
    println("Error: " + screen + " is not a valid screen");
  }
}

The draw() function is now much simpler and easier to read: it makes the flow of the program easier to understand because it doesn’t force you to understand the details of how the particular screen is drawn. The specific code for each screen is put into its

More Complex Screens

You can do more than just print messages on the screen, of course. In the following program, the “playing” screen shows a ball that bounces around the screen:

String screen;

color white = color(255);
color orange = color(255, 165, 0);

float x, y;
float dx, dy;
float diam;

PFont font;

void setup() {
  size(500, 500);

  screen = "intro";  // initial screen

  font = loadFont("AndaleMono-48.vlw");
  textFont(font);

  smooth();
  x = 100;
  y = 100;
  dx = 1;
  dy = 2;
  diam = 50;
}

void draw() {
  if (screen.equals("intro")) {
    draw_intro();
  }
  else if (screen.equals("playing")) {
    draw_playing();
  }
  else if (screen.equals("gameover")) {
    draw_gameover();
  }
  else {
    println("Error: " + screen + " is not a valid screen");
  }
}

void mouseClicked() {
  if (screen.equals("intro")) {
    screen = "playing";
  }
  else if (screen.equals("playing")) {
    screen = "gameover";
  }
  else if (screen.equals("gameover")) {
    // do nothing
  }
  else {
    println("Error: " + screen + " is not a valid screen");
  }
}

void draw_intro() {
  background(white);
  fill(255, 0, 0);
  text("Ball Demo", 50, 50);
}

void draw_playing() {
  background(white);
  noStroke();
  fill(orange);
  ellipse(x, y, diam, diam);

  x += dx;
  y += dy;

  // hit the left edge?
  if (x - diam / 2 <= 0) {
    dx = -dx;
    x = diam / 2;
  }

  // hit the right edge?
  if (x + diam / 2 >= 499) {
    dx = -dx;
    x = 499 - diam / 2;
  }

  // hit the top edge?
  if (y - diam / 2 <= 0) {
    dy = -dy;
    diam += 10;
    y = diam / 2;
  }

  // hit the bottom edge?
  if (y + diam / 2 >= 499) {
    dy = -dy;
    y = 499 - diam / 2;
  }
}

void draw_gameover() {
  background(white);
  fill(255, 0, 0);
  text("Demo Over!", 50, 50);
}

The draw_playing function contains all the code for making a ball bounce around the screen. It is essentially cut-and-pasted from previous bouncing- ball programs we’ve written.

Programming Questions

  1. Write a program with the following screens and behaviors:

    • An intro screen that says “Intro: click to continue”. When the user clicks the mouse on this screen, it changes to the playing screen.
    • A playing screen that says “Playing: click to continue”. When the user clicks the mouse on this screen, it changes to the game-over screen.
    • A game-over screen that says “Game over: click to continue”. When the user clicks the mouse on this screen, it changes to the intro screen.

    Make sure all the text messages are fully on the screen.

  2. Modify the program in the previous question so that the screens change either when the mouse is clicked or when any key is pressed.

  3. Modify the previous program so that screen changes are random. That is, when the mouse is clicked or a button is pressed, the next screen should be chosen at random from the entire set of screens (not including the current screen).