Struggling to create a selection menu

I’m working on one of the menus for my game, but I can’t seem to get it right. Here is my code so far:

#include <Arduboy2.h>
#include <EEPROM.h>

#include "sprites.h"

Arduboy2 arduboy;

uint8_t menuItemsTitle = 3;
uint8_t GameSfx = 3;
uint8_t TitleScreen = 0;
uint8_t SaveGame = 2;
uint8_t GameState = 0;
uint8_t GameStart = 1;
uint8_t cursorIndexTitle = 0;
uint8_t cursorTitleX = 88;
uint8_t cursorTitleY = 25;

#define GAME_TITLE 0
#define GAME_START 1
#define GAME_SAVE 2
#define GAME_SFX 3

void setup() {
  arduboy.begin();
  arduboy.setFrameRate(60);
  arduboy.clear();

}

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

}

void gameLoop() {
  
   switch (GameState) {

    case GAME_TITLE:
      titleScreen();
    break;

    case GAME_START:

    break;

    case GAME_SAVE:

    break;

    case GAME_SFX:

    break;

  }
}

void titleScreen() {
  arduboy.setCursor(94, 25);
  arduboy.print(F("Start"));
  arduboy.setCursor(100, 40);
  arduboy.print(F("Save"));
  arduboy.setCursor(106, 55);
  arduboy.print(F("Sfx"));
  drawCursor();

  if (arduboy.justPressed(DOWN_BUTTON) and cursorIndexTitle == 0) {
    cursorTitleX = 94;
    cursorTitleY = 40;
    cursorIndexTitle++;
  }

  if (arduboy.justPressed(DOWN_BUTTON) and cursorIndexTitle == 1) {
    cursorTitleX = 100;
    cursorTitleY = 55;
    cursorIndexTitle++;
  }

}

void drawCursor() {
  Sprites::drawOverwrite(cursorTitleX ,cursorTitleY, cursor, 0);
}

const unsigned char PROGMEM cursor[] =
{
// width, height,
4, 7,
0x7f, 0x3e, 0x1c, 0x08, 
};

There’s a few ways you could go about doing this depending on how much you already understand. The most robust ways would involve using arrays, structs and maybe pointers.

For now I’m going to assume you still don’t understand those and demonstrate a way that uses only ifs and switches, but I’m also stating for the record that this is an inefficient way of doing it.

The most important part:

constexpr uint8_t firstTitleScreenOptionIndex = 0;
constexpr uint8_t lastTitleScreenOptionIndex = 2;

constexpr uint8_t titleScreenOption0X = 94;
constexpr uint8_t titleScreenOption0Y = 25;

constexpr uint8_t titleScreenOption1X = 100;
constexpr uint8_t titleScreenOption1Y = 40;

constexpr uint8_t titleScreenOption2X = 106;
constexpr uint8_t titleScreenOption2Y = 55;

void titleScreen()
{
  // If the down button is pressed
  if(arduboy.justPressed(DOWN_BUTTON))
  {
    // If the cursor index is less than the last option index
    if(cursorIndexTitle < lastTitleScreenOptionIndex)
      // Increment the cursor index
      ++cursorIndexTitle;
  }
  
  // If the up button is pressed
  if(arduboy.justPressed(UP_BUTTON))
  {
    // If the cursor index is greater than the first option index
    if(cursorIndexTitle > firstTitleScreenOptionIndex)
      // Decrement the cursor index
      --cursorIndexTitle;
  }

  // Set the cursor's position based on the current index
  switch(cursorIndexTitle)
  {
    case 0:
      cursorTitleX = (titleScreenOption0X - 6);
      cursorTitleY = titleScreenOption0Y;
      break;
      
    case 1:
      cursorTitleX = (titleScreenOption1X - 6);
      cursorTitleY = titleScreenOption1Y;
      break;
      
    case 2:
      cursorTitleX = (titleScreenOption2X - 6);
      cursorTitleY = titleScreenOption2Y;
      break;
  }

  // Draw the options
  arduboy.setCursor(titleScreenOption0X, titleScreenOption0Y);
  arduboy.print(F("Start"));

  arduboy.setCursor(titleScreenOption1X, titleScreenOption1Y);
  arduboy.print(F("Save"));

  arduboy.setCursor(titleScreenOption2X, titleScreenOption2Y);
  arduboy.print(F("Sfx"));

  // Draw the cursor
  drawCursor();
}

The full code:

#include <Arduboy2.h>

Arduboy2 arduboy;

uint8_t menuItemsTitle = 3;
uint8_t GameSfx = 3;
uint8_t TitleScreen = 0;
uint8_t SaveGame = 2;
uint8_t GameState = 0;
uint8_t GameStart = 1;

uint8_t cursorIndexTitle = 0;
uint8_t cursorTitleX = 88;
uint8_t cursorTitleY = 25;

#define GAME_TITLE 0
#define GAME_START 1
#define GAME_SAVE 2
#define GAME_SFX 3

constexpr unsigned char PROGMEM cursor[]
{
  // width, height,
  4, 7,

  // Frame 0
  0x7F, 0x3E, 0x1C, 0x08, 
};

void setup()
{
  arduboy.begin();
}

void loop()
{
  if(!arduboy.nextFrame())
    return;

  arduboy.pollButtons();
  arduboy.clear();
  gameLoop();
  arduboy.display();
}

void gameLoop()
{
  switch(GameState)
  {
    case GAME_TITLE:
      titleScreen();
      break;

    case GAME_START:
      break;

    case GAME_SAVE:
      break;

    case GAME_SFX:
     break;
  }
}

constexpr uint8_t firstTitleScreenOptionIndex = 0;
constexpr uint8_t lastTitleScreenOptionIndex = 2;

constexpr uint8_t titleScreenOption0X = 94;
constexpr uint8_t titleScreenOption0Y = 25;

constexpr uint8_t titleScreenOption1X = 100;
constexpr uint8_t titleScreenOption1Y = 40;

constexpr uint8_t titleScreenOption2X = 106;
constexpr uint8_t titleScreenOption2Y = 55;

void titleScreen()
{
  // If the down button is pressed
  if(arduboy.justPressed(DOWN_BUTTON))
  {
    // If the cursor index is less than the last option index
    if(cursorIndexTitle < lastTitleScreenOptionIndex)
      // Increment the cursor index
      ++cursorIndexTitle;
  }
  
  // If the up button is pressed
  if(arduboy.justPressed(UP_BUTTON))
  {
    // If the cursor index is greater than the first option index
    if(cursorIndexTitle > firstTitleScreenOptionIndex)
      // Decrement the cursor index
      --cursorIndexTitle;
  }

  // Set the cursor's position based on the current index
  switch(cursorIndexTitle)
  {
    case 0:
      cursorTitleX = (titleScreenOption0X - 6);
      cursorTitleY = titleScreenOption0Y;
      break;
      
    case 1:
      cursorTitleX = (titleScreenOption1X - 6);
      cursorTitleY = titleScreenOption1Y;
      break;
      
    case 2:
      cursorTitleX = (titleScreenOption2X - 6);
      cursorTitleY = titleScreenOption2Y;
      break;
  }

  // Draw the options
  arduboy.setCursor(titleScreenOption0X, titleScreenOption0Y);
  arduboy.print(F("Start"));

  arduboy.setCursor(titleScreenOption1X, titleScreenOption1Y);
  arduboy.print(F("Save"));

  arduboy.setCursor(titleScreenOption2X, titleScreenOption2Y);
  arduboy.print(F("Sfx"));

  // Draw the cursor
  drawCursor();
}

void drawCursor()
{
  Sprites::drawOverwrite(cursorTitleX, cursorTitleY, cursor, 0);
}

You may notice that I’ve changed a few other things around slightly. I shall address most of these below:

  • The compiler couldn’t find the definition of cursor at the point where it was used in drawCursor because cursor was being declared after it was used. Generally you need to declare something before you use it, otherwise the compiler doesn’t know it exists. Arduino tries to avoid this issue by doing some preprocessing on .ino files, but frankly it’s a bit of a kludge so it struggles with some things. As a rule of thumb, it’s best to keep all your images stored at the top of your code (or better yet, when you learn how to, in a separate header file).
  • Usually you won’t need to call arduboy.setFrameRate. The default frame rate is 60, and that’s usually what you want it to be. You’d only really need to change the frame rate if you needed to use a lower frame rate because your game does too much work per frame to keep up with a higher frame rate.
  • You don’t need to call arduboy.clear in setup because you’re already calling it in loop.
  • Prefer to use && over and. and only exists for compatibility reasons and generally isn’t used by most programmers. I.e. you won’t see it much in other people’s code.
  • The brackets around arduboy.nextFrame() in if(!(arduboy.nextFrame())) are redundant. if(!arduboy.nextFrame()) has the same meaning.
    • (Note: ! is the ‘not’ operator. if(!arduboy.nextFrame()) return; can be read almost literally as “if not arduboy (dot) nextFrame then return”, i.e. “if it’s not time to draw the next frame, exit the function”. ! basically turns true to false and false to true.)
  • You should handle your game logic before you do the drawing. Drawing should be left until last, so that each frame drawn represents the game’s current state rather than the state it would have had at the end of the last frame. If you draw first and then update, the drawing represents the state of the game at the end of the previous frame, and the current state is different. That might not seem like a major issue, but it means what the player is seeing lags behind what’s actually happening, and for some people that difference might be perceivable.

If you’re prepared to give it a go, I can show you some ways that make use of structs and/or arrays, to show you how they make the code simpler.

Arrays would allow you to use cursorIndexTitle as the index into the array, which would mean instead of a big switch statement you could have just two simple lines:

cursorTitleX = (titleScreenOptionsX[cursorIndexTitle] - 6);
cursorTitleY = titleScreenOptionsY[cursorIndexTitle];

Making use of the Point struct would further reduce this to:

titleCursor.x = (titleScreenOptions[cursorIndexTitle].x - 6);
titleCursor.y = titleScreenOptions[cursorIndexTitle].y;

Or even allow you to get rid of the cursor variable in favour of a local variable in drawCursor:

Point position = titleScreenOptions[cursorIndexTitle];
Sprites::drawOverwrite(position.x - 6, position.y, cursor, 0);

(Remember: a struct type is effectively just a bundle of variables, laid out in memory one after another, thus a Point type contains both an x and a y.)

With pointers you could eventually shorten the drawing of the options down to a single loop that just iterates through a list of options that contain the position and a pointer to the text to be printed, but it would take a lot of explaining. (However, if you’re prepared to use a small library that provides some ‘magic’ functions then you wouldn’t necessarily need to understand everything.)


If you have any questions about how the code works or anything else, don’t hesitate to ask.

1 Like

I think I’m ready, could you maybe provide some example code using these methods, so I can learn how to apply them? Since I don’t know what to do with just these lines.

No, I wasn’t expecting you to. Those were just to show you how the code would end up being shorter.

I.e. this:

cursorTitleX = (titleScreenOptionsX[cursorIndexTitle] - 6);
cursorTitleY = titleScreenOptionsY[cursorIndexTitle];

Is shorter than:

switch(cursorIndexTitle)
{
  case 0:
    cursorTitleX = (titleScreenOption0X - 6);
    cursorTitleY = titleScreenOption0Y;
    break;
    
  case 1:
    cursorTitleX = (titleScreenOption1X - 6);
    cursorTitleY = titleScreenOption1Y;
    break;
    
  case 2:
    cursorTitleX = (titleScreenOption2X - 6);
    cursorTitleY = titleScreenOption2Y;
    break;
}

Right?


So, what are arrays?

Array, in English, in the sense from which the programming sense derives, means:

  • A collection laid out to be viewed in full.
  • An orderly series, arrangement or sequence.

Thus in a programming context, arrays are effectively a way to make a kind of indexable ‘list’ of variables.

In this case the variables that will be made indexable are:

constexpr uint8_t titleScreenOption0X = 94;
constexpr uint8_t titleScreenOption0Y = 25;

constexpr uint8_t titleScreenOption1X = 100;
constexpr uint8_t titleScreenOption1Y = 40;

constexpr uint8_t titleScreenOption2X = 106;
constexpr uint8_t titleScreenOption2Y = 55;

At the moment don’t worry about grouping the x and y parts, that’s what structs (in particular Points) will be used for later. For now this is about replacing those numbers.

The arrays are as thus:

constexpr uint8_t titleScreenOptionXs[] { 94, 100, 106 };
constexpr uint8_t titleScreenOptionYs[] { 25, 40, 55 };

A non-array variable follows the pattern “type name = value ;”,
whereas an array is declared as “type name [ dimension ] { elements } ;”,
where dimension is some expression that can be evaluated (at compile time) to an integer, specifying how many items may be stored in the array. Most of the time that ‘expression’ is just going to be an integer or a constexpr variable of some integer type.

Or, to put it another way, you can do things like this:

uint8_t explicitlySizedArray[10] {};

constexpr uint8_t sizeOfArray = 10;
uint8_t arrayOfIntegers[sizeOfArray] {};

However, you can also omit the dimension, in which case the array’s size will be inferred from the number of elements you provide to initialise it, which is what I did earlier:

constexpr uint8_t titleScreenOptionXs[] { 94, 100, 106 };
constexpr uint8_t titleScreenOptionYs[] { 25, 40, 55 };

If you leave out the dimension value, the compiler will automatically count the number of items in each array (in this case that’s 3 for each array) and that will be the dimension of the array.

To refer to each item in the array, you must index the array using some expression that produces an integer value. That could be a constant like 0, it could be a variable like cursorIndexTitle (which is what you’ll need to do in this case), or it could be something more complex like someVariable * 2 + 3.

In this case, I shall be replacing this:

switch(cursorIndexTitle)
{
  case 0:
    cursorTitleX = (titleScreenOption0X - 6);
    cursorTitleY = titleScreenOption0Y;
    break;
    
  case 1:
    cursorTitleX = (titleScreenOption1X - 6);
    cursorTitleY = titleScreenOption1Y;
    break;
    
  case 2:
    cursorTitleX = (titleScreenOption2X - 6);
    cursorTitleY = titleScreenOption2Y;
    break;
}

With this:

cursorTitleX = (titleScreenOptionXs[cursorIndexTitle] - 6);
cursorTitleY = titleScreenOptionYs[cursorIndexTitle];

And also all the uses of titleScreenOption0X, titleScreenOption0Y, titleScreenOption1X, titleScreenOption1Y et cetera.

Hence this:

arduboy.setCursor(titleScreenOption0X, titleScreenOption0Y);
arduboy.print(F("Start"));

arduboy.setCursor(titleScreenOption1X, titleScreenOption1Y);
arduboy.print(F("Save"));

arduboy.setCursor(titleScreenOption2X, titleScreenOption2Y);
arduboy.print(F("Sfx"));

Will become:

arduboy.setCursor(titleScreenOptionXs[0], titleScreenOptionYs[0]);
arduboy.print(F("Start"));

arduboy.setCursor(titleScreenOptionXs[1], titleScreenOptionYs[1]);
arduboy.print(F("Save"));

arduboy.setCursor(titleScreenOptionXs[2], titleScreenOptionYs[2]);
arduboy.print(F("Sfx"));

Note that array indices start at 0. There’s a technical reason for that.

(Without getting too complicated, it’s because the elements are located by adding the index to the base of the array, and the first element is actually at arrayBase + 0, not arrayBase + 1.)

Also, just in case it isn’t obvious, in this code:

cursorTitleX = (titleScreenOptionXs[cursorIndexTitle] - 6);
cursorTitleY = titleScreenOptionYs[cursorIndexTitle];

When cursorIndexTitle is 0, the first element is extracted from the array, so the expression titleScreenOptionXs[cursorIndexTitle] would evaluate to 94 because that’s the first (0th) item in that { 94, 100, 106 } as defined earlier, right? If cursorIndexTitle were 1 then you’d get 100, if cursorIndexTitle were 2 then you’d get 106, and if cursorIndexTitle were 3 then you’d get… a horrible bug called a buffer overrun.

Anyway, if you take all those pieces and put it all together with the existing code, and you end up with this:

constexpr uint8_t firstTitleScreenOptionIndex = 0;
constexpr uint8_t lastTitleScreenOptionIndex = 2;

constexpr uint8_t titleScreenOptionXs[] { 94, 100, 106 };
constexpr uint8_t titleScreenOptionYs[] { 25, 40, 55 };

void titleScreen()
{
  // If the down button is pressed
  if(arduboy.justPressed(DOWN_BUTTON))
  {
    // If the cursor index is less than the last option index
    if(cursorIndexTitle < lastTitleScreenOptionIndex)
      // Increment the cursor index
      ++cursorIndexTitle;
  }
  
  // If the up button is pressed
  if(arduboy.justPressed(UP_BUTTON))
  {
    // If the cursor index is greater than the first option index
    if(cursorIndexTitle > firstTitleScreenOptionIndex)
      // Decrement the cursor index
      --cursorIndexTitle;
  }

  // Set the cursor's position based on the current index
  cursorTitleX = (titleScreenOptionXs[cursorIndexTitle] - 6);
  cursorTitleY = titleScreenOptionYs[cursorIndexTitle];

  // Draw the options
  arduboy.setCursor(titleScreenOptionXs[0], titleScreenOptionYs[0]);
  arduboy.print(F("Start"));

  arduboy.setCursor(titleScreenOptionXs[1], titleScreenOptionYs[1]);
  arduboy.print(F("Save"));

  arduboy.setCursor(titleScreenOptionXs[2], titleScreenOptionYs[2]);
  arduboy.print(F("Sfx"));

  // Draw the cursor
  drawCursor();
}

Or, in full:

#include <Arduboy2.h>
#include <EEPROM.h>

#include "sprites.h"

Arduboy2 arduboy;

uint8_t menuItemsTitle = 3;
uint8_t GameSfx = 3;
uint8_t TitleScreen = 0;
uint8_t SaveGame = 2;
uint8_t GameState = 0;
uint8_t GameStart = 1;

uint8_t cursorIndexTitle = 0;
uint8_t cursorTitleX = 88;
uint8_t cursorTitleY = 25;

#define GAME_TITLE 0
#define GAME_START 1
#define GAME_SAVE 2
#define GAME_SFX 3

constexpr unsigned char PROGMEM cursor[]
{
  // width, height,
  4, 7,

  // Frame 0
  0x7F, 0x3E, 0x1C, 0x08, 
};

void setup()
{
  arduboy.begin();
}

void loop()
{
  if(!arduboy.nextFrame())
    return;

  arduboy.pollButtons();
  arduboy.clear();
  gameLoop();
  arduboy.display();
}

void gameLoop()
{
  switch(GameState)
  {
    case GAME_TITLE:
      titleScreen();
      break;

    case GAME_START:
      break;

    case GAME_SAVE:
      break;

    case GAME_SFX:
     break;
  }
}

constexpr uint8_t firstTitleScreenOptionIndex = 0;
constexpr uint8_t lastTitleScreenOptionIndex = 2;

constexpr uint8_t titleScreenOptionXs[] { 94, 100, 106 };
constexpr uint8_t titleScreenOptionYs[] { 25, 40, 55 };

void titleScreen()
{
  // If the down button is pressed
  if(arduboy.justPressed(DOWN_BUTTON))
  {
    // If the cursor index is less than the last option index
    if(cursorIndexTitle < lastTitleScreenOptionIndex)
      // Increment the cursor index
      ++cursorIndexTitle;
  }
  
  // If the up button is pressed
  if(arduboy.justPressed(UP_BUTTON))
  {
    // If the cursor index is greater than the first option index
    if(cursorIndexTitle > firstTitleScreenOptionIndex)
      // Decrement the cursor index
      --cursorIndexTitle;
  }

  // Set the cursor's position based on the current index
  cursorTitleX = (titleScreenOptionXs[cursorIndexTitle] - 6);
  cursorTitleY = titleScreenOptionYs[cursorIndexTitle];

  // Draw the options
  arduboy.setCursor(titleScreenOptionXs[0], titleScreenOptionYs[0]);
  arduboy.print(F("Start"));

  arduboy.setCursor(titleScreenOptionXs[1], titleScreenOptionYs[1]);
  arduboy.print(F("Save"));

  arduboy.setCursor(titleScreenOptionXs[2], titleScreenOptionYs[2]);
  arduboy.print(F("Sfx"));

  // Draw the cursor
  drawCursor();
}

void drawCursor()
{
  Sprites::drawOverwrite(cursorTitleX, cursorTitleY, cursor, 0);
}

If you’re happy with that then I can show you how to incorporate the Point type.

If not, feel free to ask any questions.
(Though it’ll be at least an hour or so before I can answer them, as I have an appointment to be at.)

I can also show you other examples of things you can do with arrays if you want, but at the moment I think that’s the only thing pertinent to your menu problem.

Once you’re more familiar with the Point type and other structs you might want to revise your first game to use arrays of Points or your own struct Enemy to help simplify things.

1 Like

Thanks! It’s working just how I wanted, and I understand it completely. I would love to see how to incorporate the point type as well.

1 Like

Just to confirm, did you figure out that part you quoted and then edited out of your response?

If not I’m happy to go over it.


So, firstly a struct is a way of structuring your data.
The struct declaration itself is like a blueprint specifying what data the newly declared type will hold.
After you have declared a struct type, any instance (e.g. variable) of that type will contain several member variables, which in effect are themselves variables. Thus a struct is like a structured ‘bag’ of data.

For example, a Point type, containing both an x and a y member variable, each of type int16_t (i.e. the 16-bit signed integer type) could be declared as:

struct Point
{
	int16_t x;
	int16_t y;
};

(And in fact, that’s exactly how Point was originally defined in the Arduboy2 library. At least, they were until I came along with my fancy notion of ‘constructors’, so now it looks like this, but that’s another story…)

If you had such a type, you could do things like this:

// Declares the variable 'cursorPosition' to be of type 'Point'
// with a value of 10 for 'x' and 20 for 'y'
Point cursorPosition { 10, 20 };

// Prints 10
arduboy.println(cursorPosition.x);

// Prints 20
arduboy.println(cursorPosition.y);

The . is the ‘member of object’ operator, which accesses members (e.g. member functions) of objects (e.g. variables), hence if cursorPosition is of type Point, you can use . to access its member variables, e.g. cursorPosition.x accesses the x member variable of cursorPosition.

So, how does this help you?
Well, what if instead of managing two separate arrays for x and y values, you just managed one?

Which is to say that what if instead of:

constexpr uint8_t titleScreenOptionXs[] { 94, 100, 106 };
constexpr uint8_t titleScreenOptionYs[] { 25, 40, 55 };

You could do:

constexpr Point titleScreenOptions[] { { 94, 25 }, { 100, 40 }, {106, 55} };

Spoiler: you can. You can combine points and arrays to do just that.
And now instead of having an array of xs and an array of ys, you have an array of points (Point), and each point consists of both an x (x) and a y (y).

Which in turn means that instead of:

cursorTitleX = (titleScreenOptionXs[cursorIndexTitle] - 6);
cursorTitleY = titleScreenOptionYs[cursorIndexTitle];

You can do:

cursorTitleX = (titleScreenOptions[cursorIndexTitle].x - 6);
cursorTitleY = titleScreenOptions[cursorIndexTitle].y;

In fact, if it weren’t for that pesky - 6, you could have replaced uint8_t cursorTitleX = 88; and uint8_t cursorTitleY = 25; with Point titleCursor { 88, 25 }; (or Point titleCursor = { 88, 25 };, if you’d rather), and then you would have been able to do:

titleCursor = titleScreenOptions[cursorIndexTitle];

Because when you assign a struct you assign the whole struct - every member variable. (Just imagine how much work that will save you.)

Alas, you still have that - 6, and it’s probably not worth getting rid of. (Though you could if you wanted, e.g. by moving the - 6 into the drawing code.)

But you could still replace cursorTitleX and cursorTitleY anyway.
Why keep track of two variables when you can keep track of just the one?

I won’t do that for now, but if you want I can show you how.
(Or you could have a go yourself even.)

Anyway, with that all said there’s only one more thing to account for: the setCursor calls will have to be updated.

Sadly you can’t just do arduboy.setCursor(titleScreenOptions[0]) because the Arduboy2 class doesn’t have a version (the technical term is function overload) of setCursor that would accept a Point. (But it would probably be worth adding one, so I’m going to raise a GitHub issue about it…)

So instead this:

// Draw the options
arduboy.setCursor(titleScreenOptionXs[0], titleScreenOptionYs[0]);
arduboy.print(F("Start"));

arduboy.setCursor(titleScreenOptionXs[1], titleScreenOptionYs[1]);
arduboy.print(F("Save"));

arduboy.setCursor(titleScreenOptionXs[2], titleScreenOptionYs[2]);
arduboy.print(F("Sfx"));

Will become:

arduboy.setCursor(titleScreenOptions[0].x, titleScreenOptions[0].y);
arduboy.print(F("Start"));

arduboy.setCursor(titleScreenOptions[1].x, titleScreenOptions[1].y);
arduboy.print(F("Save"));

arduboy.setCursor(titleScreenOptions[2].x, titleScreenOptions[2].y);
arduboy.print(F("Sfx"));

(If only there were a way to store those strings too, then you could replace those lines with some kind of loop that runs through the array…)

Meaning the important code becomes:

constexpr uint8_t firstTitleScreenOptionIndex = 0;
constexpr uint8_t lastTitleScreenOptionIndex = 2;

constexpr Point titleScreenOptions[] { { 94, 25 }, { 100, 40 }, {106, 55} };

void titleScreen()
{
  // If the down button is pressed
  if(arduboy.justPressed(DOWN_BUTTON))
  {
    // If the cursor index is less than the last option index
    if(cursorIndexTitle < lastTitleScreenOptionIndex)
      // Increment the cursor index
      ++cursorIndexTitle;
  }
  
  // If the up button is pressed
  if(arduboy.justPressed(UP_BUTTON))
  {
    // If the cursor index is greater than the first option index
    if(cursorIndexTitle > firstTitleScreenOptionIndex)
      // Decrement the cursor index
      --cursorIndexTitle;
  }

  // Set the cursor's position based on the current index
  cursorTitleX = (titleScreenOptions[cursorIndexTitle].x - 6);
  cursorTitleY = titleScreenOptions[cursorIndexTitle].y;

  // Draw the options
  arduboy.setCursor(titleScreenOptions[0].x, titleScreenOptions[0].y);
  arduboy.print(F("Start"));

  arduboy.setCursor(titleScreenOptions[1].x, titleScreenOptions[1].y);
  arduboy.print(F("Save"));

  arduboy.setCursor(titleScreenOptions[2].x, titleScreenOptions[2].y);
  arduboy.print(F("Sfx"));

  // Draw the cursor
  drawCursor();
}

And the full code is now:

#include <Arduboy2.h>

Arduboy2 arduboy;

uint8_t menuItemsTitle = 3;
uint8_t GameSfx = 3;
uint8_t TitleScreen = 0;
uint8_t SaveGame = 2;
uint8_t GameState = 0;
uint8_t GameStart = 1;

uint8_t cursorIndexTitle = 0;
uint8_t cursorTitleX = 88;
uint8_t cursorTitleY = 25;

#define GAME_TITLE 0
#define GAME_START 1
#define GAME_SAVE 2
#define GAME_SFX 3

constexpr unsigned char PROGMEM cursor[]
{
  // width, height,
  4, 7,

  // Frame 0
  0x7F, 0x3E, 0x1C, 0x08, 
};

void setup()
{
  arduboy.begin();
}

void loop()
{
  if(!arduboy.nextFrame())
    return;

  arduboy.pollButtons();
  arduboy.clear();
  gameLoop();
  arduboy.display();
}

void gameLoop()
{
  switch(GameState)
  {
    case GAME_TITLE:
      titleScreen();
      break;

    case GAME_START:
      break;

    case GAME_SAVE:
      break;

    case GAME_SFX:
     break;
  }
}

constexpr uint8_t firstTitleScreenOptionIndex = 0;
constexpr uint8_t lastTitleScreenOptionIndex = 2;

constexpr Point titleScreenOptions[] { { 94, 25 }, { 100, 40 }, {106, 55} };

void titleScreen()
{
  // If the down button is pressed
  if(arduboy.justPressed(DOWN_BUTTON))
  {
    // If the cursor index is less than the last option index
    if(cursorIndexTitle < lastTitleScreenOptionIndex)
      // Increment the cursor index
      ++cursorIndexTitle;
  }
  
  // If the up button is pressed
  if(arduboy.justPressed(UP_BUTTON))
  {
    // If the cursor index is greater than the first option index
    if(cursorIndexTitle > firstTitleScreenOptionIndex)
      // Decrement the cursor index
      --cursorIndexTitle;
  }

  // Set the cursor's position based on the current index
  cursorTitleX = (titleScreenOptions[cursorIndexTitle].x - 6);
  cursorTitleY = titleScreenOptions[cursorIndexTitle].y;

  // Draw the options
  arduboy.setCursor(titleScreenOptions[0].x, titleScreenOptions[0].y);
  arduboy.print(F("Start"));

  arduboy.setCursor(titleScreenOptions[1].x, titleScreenOptions[1].y);
  arduboy.print(F("Save"));

  arduboy.setCursor(titleScreenOptions[2].x, titleScreenOptions[2].y);
  arduboy.print(F("Sfx"));

  // Draw the cursor
  drawCursor();
}

void drawCursor()
{
  Sprites::drawOverwrite(cursorTitleX, cursorTitleY, cursor, 0);
}

(And it actually compiles and works too. Usually I’m too lazy to compile and test the code I hand out, but I’ve been on my best behaviour today. :P)


So, with that all said and done…

Does that all make sense?
Do you have any questions?
Am I going too fast and/or dumping too much information at once?

1 Like

It does make sense, merging the x and y arrays with point. I just have one more question, how would I be able to press a button, when the cursor has the start game option selected, because after the player clicks start, I want to have another menu after that, very similar to the title menu, but this one will be for the game part.

Well, you already know how to check if a button is pressed,
so let me ask you two questions to see if you can work it out:

  • Which variable tells you which option is currently selected?
  • Using that variable, how would you know when the start option is selected?
    • Hint: What value would that variable have when ‘start’ is the selected option?

Before, I did

if (arduboy.justPressed(A_BUTTON) && cursorIndexTitle == 0) {
  gameState = GAME_MENU;
}

But when I pressed A, nothing happened. Even though I had code for printing text in my gameMenu function. So I think maybe I’m missing something. Like, do I add all the states, old/new to the gameLoop, or make a new switch, for the menu after you press start?

EDIT: figured it out this time. Must have done something wrong last time.

1 Like

Oops, almost forgot to check back.

At least you figured it out this time.

The code in the main post didn’t actually alter gameState, so it could be that.
Or maybe you forgot to save the code before uploading it or something like that.


Anyway, I’m not sure how you’re doing it, but one way would be to do:

if (arduboy.justPressed(A_BUTTON))
{
	switch(cursorIndexTitle)
	{
		case 0: // Start
			gameState = GAME_START;
			break;
			
		case 1: // Save
			gameState = GAME_SAVE;
			break;
			
		case 2: // Sfx
			gameState = GAME_SFX;
			break;
	}
}

Although, if all you’re doing in each case is setting the gameState variable, then you could alternatively use an array.

// Put at the top level with your globals
constexpr int titleStateOptions[] { GAME_START, GAME_SAVE, GAME_SFX };

And then:

if (arduboy.justPressed(A_BUTTON))
{
	gameState = titleStateOptions[cursorIndexTitle];
}

Arrays are really handy when you start having to deal with multiples of things.


While I think of it, when you’ve got used to enum classes, you might want to try using one for your game state.

(Again, if you need more info or don’t understand something, feel free to ask.

1 Like

Yeah, last time, when I was doing the if-statement to change the gamestate to the next menu, I did “GameState::GameMenu”, which was wrong, but now, I did “gameState = GameState::GameMenu” which worked. I also changed all the #define code, to an enum class. Heres my code so far in case you wanted to sneak a peak.

1 Like

That’s a large amount of progress in a short space of time.

Obviously it’ll take some practice before you get used to all the things you’ve been exposed to and how they work, but hopefully the fact you’ve got them in your code will give you chance to get used to them.

(Don’t hesitate to play around with them and break things. Whenever you upload a copy of your code to GitHub it stores a snapshot of the entire code state, so you can always access older versions as long as you uploaded a copy of the code at the point you want to go back to.)


While I notice them, I’ll point out three last things before I retire for the evening:

  • The arduboy.justPressed(A_BUTTON) && cursorIndexTitle == 2 part in SfxToggle is redundant because you’re already checking that before you call it.
    • (And really you want the condition to be separate from SfxToggle anyway, in case you need to toggle the sfx in other circumstances.)
  • There’s probably a better way to toggle the sound on and off than what you have planned for sfxToggle, but there’s probably not much point going into detail until you’re actually at the point where you’re adding sounds to the game.
  • Finally, do you remember what I said about what ! does? If so, perhaps you’ll have an idea what sfxToggle = !sfxToggle; does, and maybe even why.
1 Like

IIRC, “!” is the not operator. So I assume “sfxToggle = !sfxToggle” makes sfxToggle the opposite of what it currently is?

1 Like

Correct. 

2 Likes

Indeed. But it’s the why that’s the important bit.

Remember that I said the not operator (!) turns true into false and false into true.
So if sfxToggle is true then !sfxToggle is false, and sfxToggle is false then !sfxToggle is true
Hence sfxToggle = !sfxToggle; assigns sfxToggle the value of its opposite.

If you can get to grips with that idea then you’ll find lots of places where ! can help you simplify your code.

1 Like