How to include delays/time freezes? How to create high score implementation?

I am working on a new game (I decided a platformer was too much for my current skill level) and when you start the game, bouncing balls start start coming at you and make you lose. What I would like to do, is after drawing all the rectangles/sprites, I want to add some sort of temporary time freeze/delay, so that the player can notice where the balls are, and when the delay ends, they can move the player out of the way. Is this possible to code?

You could use a FOR loop to make a short timer.
There is a delay function but i would not use it as it stalls everything.

So something like a variable timer? For example, I could create a float variable set to zero, and add 0.5 (or anything to increase the variable’s value) then once the variable gets bigger than a certain value, the game could start? Also, don’t want to pollute the forum with many threads like this, so I’m going to ask it here. How would one incorporate a high score function?

I added a score counter as well as a way to increase your score, and since it’s an integer, should I use EEPROM.update or EEPROM.put? I’m trying to print the score (or value of the variable) and save it.

Honestly, I think this is a smart move.
Platformers are much trickier than people give them credit for.

Yes. As @Vampirics mentioned there is a delay function, but it’s not something you ought to use because it delays the entire CPU, which would mess with the framerate (and possibly the sound if you add any music).

The best way is to add a new ‘game delay’ state to your state machine and to count the time manually, either by counting frames or by counting actual elapsed time using the millis function.

Unfortunately what’s going to complicate things is that you haven’t separated your drawing code from your game logic. If you had the code that draws the level in a separate function then you could call that same function from both the game play state and the waiting state. (This is called “Separation of Concerns”.)

I started making an edit of your code, but that was before you just updated it a few minutes ago with new code (which, by the way, has a bug - your for loop in titlescreen is broken).

I’m just going to run my edits to see if they work and then post the code and hopefully explain it and answer any questions (though it’s nearly 1am and I’m very tired, so maybe not).


Here’s the edited code with a timer:

#include <Arduboy2.h>
#include "images.h"

Arduboy2 arduboy;

int gamestate = 0;
int carx = 20;
int cary = 15;
int carsize = 9;
int enemyy = 0;
int enemyx = 123;
int enemysize = 5;
int enemyright = -1;
bool cardirection = false;
int enemyX = 0;
int enemyY = 0;
int enemySize = 5;
int enemyX2 = 0;
int enemyY2 = 0;
int enemySize2 = 5;
int enemyDown2 = 1;
int enemyRight2 = 1;
int enemydown = 1;

// Target time for the delay timer
uint32_t delayTarget = 0;

#define GAME_TITLE 0
#define GAME_DELAY 4
#define GAME_PLAY 1
#define GAME_OVER 2
#define GAME_HIGH 3

void titlescreen() {
  if (arduboy.justPressed(A_BUTTON)) {
    enemyy = random(11, 54);
    enemyX = random(11, 113);

    // Set the target time to be the current time
    // plus 4000ms (i.e. 4 seconds)
    delayTarget = (millis() + (4 * 1000));

    // Change the state to the delay state
    gamestate = GAME_DELAY;
  }

  if (arduboy.justPressed(B_BUTTON)) {
    gamestate = GAME_HIGH;
  }
}

void drawPlayerCar()
{
  // This can be simplified further
  if (cardirection)
  {
    Sprites::drawOverwrite(carx, cary, car, 1);
  }
  else
  {
    Sprites::drawOverwrite(carx, cary, car, 0);
  }
}

// Does what it says on the tin
void drawEnemies()
{
  arduboy.fillRect(enemyx, enemyy, enemysize, enemysize, WHITE);
  arduboy.fillRect(enemyX, enemyY, enemySize, enemySize, WHITE);
  arduboy.fillRect(enemyX2, enemyY2, enemySize2, enemySize2, WHITE);
}

// Draws the cars and enemies
void drawGame()
{
  drawPlayerCar();
  drawEnemies();
}

void updateGameDelayState()
{
  // Get the current time
  uint32_t current = millis();

  // If the current time is greater than or equal to
  // the target time, then 4 seconds has elapsed
  if(current >= delayTarget)
  {
      // Change to the game play state to begin the game
      gamestate = GAME_PLAY;
  }

  // Calculate the number of seconds remaining
  int remainingSeconds = ((delayTarget - current) / 1000);

  // Print the number of seconds remaining
  arduboy.print(remainingSeconds);
  arduboy.println(F(" seconds remaining..."));

  drawGame();
}

void handlePlayerInput()
{
  if (arduboy.pressed(DOWN_BUTTON))
  {
    cary += 1;
    cardirection = true;
  }

  if (arduboy.pressed(UP_BUTTON))
  {
    cary -= 1;
    cardirection = true;
  }

  if (arduboy.pressed(RIGHT_BUTTON))
  {
    carx += 1;
    cardirection = false;
  }

  if (arduboy.pressed(LEFT_BUTTON))
  {
    carx -= 1;
    cardirection = false;
  }
}

void moveEnemies()
{

  if (enemyright == 1) {
    enemyx = enemyx + 1;
  }

  if (enemyright == -1) {
    enemyx = enemyx - 1;
  }

  if (enemyx == 123) {
    enemyright = -1;
  }

  if (enemyx == 0) {
    enemyright = 1;
  }

  if (enemydown == 1) {
    enemyY = enemyY + 1;
  }

  if (enemydown == -1) {
    enemyY = enemyY - 1;
  }
  
  if (enemyY == 59) {
    enemydown = - 1;
  }

  if (enemyY == 0) {
    enemydown = 1;
  }

  if (enemyDown2 == 1) {
    enemyY2 = enemyY2 + 1;
  }

  if (enemyDown2 == -1) {
    enemyY2 = enemyY2 - 1;
  }

  if (enemyRight2 == 1) {
    enemyX2 = enemyX2 + 1;
  }

  if (enemyRight2 == -1) {
    enemyX2 = enemyX2 - 1;
  }

  if (enemyY2 == 0) {
    enemyDown2 = 1;
  }

  if (enemyY2 + enemySize2 == 63) {
    enemyDown2 = - 1;
  }

  if (enemyX2 == 0) {
    enemyRight2 = 1;
  }

  if (enemyX2 + enemySize2 == 123) {
    enemyRight2 = - 1;
  }
}

void handleCollisions()
{
  Rect player { carx, cary, carsize, carsize };
  
  Rect enemy { enemyx, enemyy, enemysize, enemysize };

  if (arduboy.collide(player, enemy))
  {
    gamestate = GAME_OVER;

    // No need to test any more collisions,
    // so exit the function early
    return;
  }

  Rect enemY { enemyX, enemyY, enemySize, enemySize };

  if (arduboy.collide (player, enemY))
  {
    gamestate = GAME_OVER;

    // No need to test any more collisions,
    // so exit the function early
    return;
  }

  Rect enemY2 (enemyX2, enemyY2, enemySize2, enemySize2);
}

void gameplay()
{
  // Defer to other functions,
  // to avoid thinking about too much at once.
  handlePlayerInput();
  moveEnemies();
  handleCollisions();
  drawGame();
}

void gameoverscreen() {
  
}

void highscorescreen() {
  if (arduboy.justPressed(B_BUTTON)) {
    gamestate = GAME_TITLE;
  }
}

void gameloop() {

  switch(gamestate) {
    
    case GAME_TITLE:
      titlescreen();
      break;

    case GAME_DELAY:
      updateGameDelayState();
      break;

    case GAME_PLAY:
      gameplay();
      break;

    case GAME_OVER:
      gameoverscreen();
      break;

    case GAME_HIGH:
      highscorescreen();
      break;
      
  }
}

void setup() {
  arduboy.begin();
  arduboy.setFrameRate(45);
  arduboy.initRandomSeed();
  arduboy.clear();

}

void loop() {
  if (!arduboy.nextFrame())
    return;
  
  arduboy.clear();
  arduboy.pollButtons();
  gameloop();
  arduboy.display();
}

So basically:

  • There’s a new game state that implements the delay
  • I’ve moved the code that draws the player car and enemies into a separate function, so it can be called from both the gameplay state and the new delay state
  • When switching to the delay state, the titlescreen also prepares a target time 4 seconds in the future
  • The delay state basically does nothing until the current time is greater than or equal to the target time, at which point 4 seconds will have elapsed
  • When the 4 seconds have elapsed, the delay state changes to the gameplay state
  • I added a message that says how many seconds remain, because I presumed that was going to be the next thing you’d be asking about
  • While I was at it, I split the gameplay function up into four smaller functions because it had a lot going on
    • You seem to be missing a collision check for the third enemy by the way. I didn’t add one because I wasn’t sure if that was intentional or not
    • You were drawing the player car twice. I presumed that was a bug and fixed that, so now it’s only drawn once, after all the game logic has been processed.

If anything is confusing you or you have any questions, feel free to ask and I’ll answer in the morning if someone else doesn’t get there first.


EEPROM.put because it works with (more or less) all types.
EEPROM.update only works with single byte values (I think).

1 Like

Correct > https://docs.arduino.cc/learn/built-in-libraries/eeprom

@Ard_Flamingo, just so you know, I’ve made a fork of your code with my changes to make it easier to see what I changed:

I’d offer to merge this with your current code, but I’m not sure it would merge properly - some of the changes you made since the one I based this on might conflict.

I can make a version based on your current code later if you want.

1 Like