How do I display a message for a few seconds?

edited November 2014 in Common Questions

How do I display a message for a few seconds?

That's a question I see coming up regularly, people attempting to display a message, but saying it only flashes briefly on screen.

To show how to do it properly, we need a simple sketch to build upon. Say we use the good old bouncing ball:

// Ball handling
float posX, posY; // Position
float speedX, speedY; // Movement (linear)
int radius; // Radius of the ball
color ballColor; // And its color

void setup()
{
  size(600, 400);
  smooth();

  // Initialize the ball's data
  posX = 120;
  posY = 50;
  speedX = -2;
  speedY = 3;
  radius = 24;
  ballColor = #002277;
}

void draw()
{
  // Erase the sketch area
  background(#AAFFEE);
  // Compute the new ball position
  moveBall();
  // And display it
  displayBall();
}

void moveBall()
{
  // Move by the amount determined by the speed
  posX += speedX;
  // Check the horizontal position against the bounds of the sketch
  if (posX < radius || posX > width - radius)
  {
    // 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 = -speedX;
    posX += speedX;
  }
  // Idem for the vertical speed/position
  posY += speedY;
  if (posY < radius || posY > height - radius)
  {
    speedY = -speedY;
    posY += speedY;
  }
}

void displayBall()
{
  // Simple filled circle
  noStroke();
  fill(ballColor);
  ellipse(posX, posY, radius * 2, radius * 2);
}

Now, say that if the user clicks on the moving ball, we display a message, then we continue. We need to display a text, so we set up the font in setup():

  PFont f = createFont("Arial", 48);
  textFont(f);

It can be replaced by a simple textSize(48) in recent versions of Processing, now using a default font if none is provided. Note also I use createFont() because it is convenient (no need to create stuff in the data folder) but to make a more portable sketch across system (that might not have this font), you better use loadFont().

What not to do

We can now add the management of the mouse click:

void mousePressed()
{
  // If mouse position is close of the center of the ball (less than the radius in distance)
  if (dist(mouseX, mouseY, posX, posY) < radius)
  {
    // We have a hit, and indicate it
    fill(#FFAA88);
    text("You got it!", 150, height / 2);
  }
}

When running it, you can see where this code is faulty: at best, we see the text flashing briefly. That's because the click event happens somewhere in the middle of a frame, and updates it. But the next frame, some milliseconds later, just erases the text.

There are several ways to address the issue, some of them are flawed...

New users often try or attempt wrong approaches, we will briefly examine them here.

The common attempt is to use delay(), which stops the sketch for the given duration. This is so bad that the function almost disappeared from Processing 2.0 (but it was kept because it is useful with the Serial library). delay() literally stops the sketch: no secondary animation is possible, counters are halted, the user cannot click or hit a key, etc. This "stop the world" approach is to avoid in general.

Users often ask, in the forum, if we can reset the millis() value, ie. set it to zero. It is just not possible (or wanted), as it counts the number of milliseconds spent since the start of the sketch. But it is a step in the right direction, as we will see below.

The proper way to do it

One of the most flexible and extensible to do it properly is to use states, aka. a state machine.

Here, it can be reduced to its simplest form, using a simple boolean, bDisplayMessage, that we declare at global level. We also need to track time to be sure the message is displayed long enough.

boolean bDisplayMessage; // False by default
int startTime; // The (last) time when the mouse have been clicked
final int DISPLAY_DURATION = 1000; // in milliseconds = 1s

We move the drawing of the message to draw():

void draw()
{
  background(#AAFFEE);
  if (bDisplayMessage)
  {
    fill(#FFAA88);
    text("You got it!", 150, height / 2);
  }
  else
  {
    moveBall();
    displayBall();
  }
}

and then the mouse handler just sets the boolean:

void mousePressed()
{
  // Better than if (cond) b = true; else b = false; !
  bDisplayMessage = dist(mouseX, mouseY, posX, posY) < radius;
}

Now we just have to stop displaying the message and to resume the game after a while. We do the check in draw(), see it in the complete code (stripped of most comments for brevity):

// Ball handling
float posX, posY;
float speedX, speedY;
int radius;
color ballColor;

boolean bDisplayMessage;
int startTime;
final int DISPLAY_DURATION = 1000; // 1s


void setup()
{
  size(600, 400);
  smooth();

  PFont f = createFont("Arial", 48);
  textFont(f);

  posX = 120;
  posY = 50;
  speedX = -2;
  speedY = 3;
  radius = 24;
  ballColor = #002277;
}

void draw()
{
  background(#AAFFEE);
  if (bDisplayMessage)
  {
    fill(#FFAA88);
    text("You got it!", 150, height / 2);
    // If the spent time is above the defined duration
    if (millis() - startTime > DISPLAY_DURATION)
    {
      // Stop displaying the message, thus resume the ball moving
      bDisplayMessage = false;
    }
  }
  else
  {
    moveBall();
    displayBall();
  }
}

void mousePressed()
{
  bDisplayMessage = dist(mouseX, mouseY, posX, posY) < radius;
  // Record the time of the event
  startTime = millis();
}

// Same moveBall/displayBall code, see above

In the next articles, we will see how to how to handle more states, and use classes to make this sketch scalable (using more balls).

Tagged:
Sign In or Register to comment.