Basic Time Functions

Many Blinks games use time-based mechanics. And unlike many board games, where timers need to be used to enforce these mechanics, Blinks can do this themselves! In this tutorial we’ll go over the two major ways of using time, as well as some convenience functions that can help.

Let’s start with the “millis()” function. If you call millis() in your script, it will return the milliseconds elapsed since the script began. This is essentially a stopwatch that starts when the game starts, and never ends. Okay, technically it rolls back to 0 after a huge amount of time, but for our purposes it just counts up. This is useful in a couple of ways, like tracking the timing of an event in your script, but mostly we use it to make pulses. Let’s do that now.

We’re going to use the dim() function we learned earlier to dim our Blinks in a pulsing pattern. We know the dim command only accepts values from 0 to 255, so we’ll truncate our values to match like this:

void loop() {
  byte dimness = millis() % 256;
  setColor(dim(WHITE, dimness));
}

Install that on a Blink and it’ll give you very rapid fade up to white over and over again. There are two ways we might want to adjust this pattern. For starters, it might be nice if it didn’t drop immediately from full brightness to off each time. We can solve that by using a sine wave pattern, which we can conventiently get by using the built in sin8_C() function, like this:

byte dimness = sin8_C(millis() % 256);

Put this back on your Blink and the pattern will now be a smooth transition from off to on and back off again. This is because the sin8_C() command takes a byte from 0-255 and transforms it using a sin function. Before our value went from 0-255 in a linear fashion, then back to 0. Transformed, it goes from 0 up to 255 and back to 0 in the same time.

But it’s kinda hard to make out the smoothness with how fast it’s moving. We need to figure out a way to make the pulse time… longer. There are many ways you could do this using simple arithmetic or other methods, but the Blinks library has another great convenience function that would help here. Before we implement it, we’ll just do a bit more setup.

At the top of the script, we’re going to define a “PULSE_LENGTH” like this:

#define PULSE_LENGTH 2000

Then we’re going to change the code in our loop to look like this:

void loop() {
  //get progress from 0 - MAX
  int pulseProgress = millis() % PULSE_LENGTH;

  //transform that progress to a byte (0-255)
  byte pulseMapped = map(pulseProgress, 0, PULSE_LENGTH, 0, 255);

  //transform that byte with sin
  byte dimness = sin8_C(pulseMapped);

  //set color
  setColor(dim(WHITE, dimness));
}

If you haven’t already, install that onto your Blink so you can see your smooth 2-second pulse. That new piece of code you’re seeing is the map() function. It is probably the most convenient of the convenience functions, and you will see it pretty much everywhere in our code. What it does is it takes a number, in this case “pulseProgress,” which lies between an input minimum and an input maximum, in this case 0 and PULSE_LENGTH, then maps it to the number it would be between an output minimum and and output maximum, in this case 0 and 255. So, for example, if pulseProgress was 1000, that would put it halfway between 0 and PULSE_LENGTH, which is defined as 2000. Therefore its output value, mapped to 0-255, would be 127, which is halfway between the two.

This function has some restrictions, though. For instance, minimums and maximums must maintain a low-to-high relationship - you can’t get tricky and try to set a higher number as a minimum and get something to map in reverse. You’ll need to do that after the fact. In addition, we don’t recommend inputting mapping values that are beyond the bounds of the input min and max - not that it won’t work, only that it’s not designed to work that way and can cause issues in some cases. But for the most part, this shouldn’t be an issue. Frankly, in most cases you’ll be using it to do exactly what we’re doing here: mapping a big number down to 0-255 so you can use it in visual codes.

Now it’s time for us to move to the next big way we deal with time, and that is by using Timers. Timers work pretty much exactly how you’d expect, so I’m just going to make a super quick example that uses all three timer commands, and adds an interesting little flourish to our existing code.

We’ll start by declaring a timer at the top, along with a new duration definition for that timer.

#define TIMER_LENGTH 6000
Timer redTimer;

We’ll also use the first Timer command, Timer.set(), in the setup function:

void setup() {
  redTimer.set(TIMER_LENGTH);
}

This sets the timer to run for the allotted number of milliseconds, which in this case is 6000, or 6 seconds. What we’re going to do now is turn the Blink red while the timer is running. To do this, we need to know if the timer is still running, and we’ll do that by checking if the Timer is expired in loop(). If it is, we’ll adjust the way we display color. This is how we’ll update the code:

//check if timer is expired
  byte saturation = 0;
  if (!redTimer.isExpired()) {
    saturation = 255;
  }

  //set color
  setColor(makeColorHSB(0, saturation, dimness));

isExpired() returns true if the Timer has run out of time, and false if it’s still going. For our purposes, we’re actually more interested in times when it’s not expired, that’s why we’ve thrown the “!” before our statement. This code will change the color of the Blink to red in the first 6 seconds after you start the code. But wouldn’t it be cooler if instead of just staying solidly red, it faded back to white? Of course it would, and we can do that by slowly changing the saturation over time. We’ll get the values we need by using the last Timer function, Timer.getRemaining(). This returns the amount of time remaining on the timer, from the full set time to 0. Let’s take that time and map it 0-255 using the map function, like this:

  byte saturation = 0;
  if (!redTimer.isExpired()) {
    int timerProgress = redTimer.getRemaining();
    saturation = map(timerProgress, 0, TIMER_LENGTH, 0, 255);
  }

If you install this code, you’ll see the flashes start out red, then slowly desaturate to white over the first 6 seconds. It’s not necessarily the best-looking effect, but through experimentation you can find all sorts of interesting fades and effects.

And those are all the time-based functions, plus some other convenience functions to boot! As always, here’s the full code of what we made in case you missed a step.

#define PULSE_LENGTH 2000
#define TIMER_LENGTH 6000
Timer redTimer;

void setup() {
  redTimer.set(TIMER_LENGTH);
}

void loop() {
  //get progress from 0 - MAX
  int pulseProgress = millis() % PULSE_LENGTH;

  //transform that progress to a byte (0-255)
  byte pulseMapped = map(pulseProgress, 0, PULSE_LENGTH, 0, 255);

  //transform that byte with sin
  byte dimness = sin8_C(pulseMapped);

  //check if timer is expired
  byte saturation = 0;
  if (!redTimer.isExpired()) {
    int timerProgress = redTimer.getRemaining();
    saturation = map(timerProgress, 0, TIMER_LENGTH, 0, 255);
  }

  //set color
  setColor(makeColorHSB(0, saturation, dimness));
}
1 Like