My first roguelike development blog - [game programming blog ~ part 3]

My roguelike development blog ~ part 3


In my previous two tutorials I have showed an example on how to write a main runtime loop and your game loop. Now it's time to incorporate a customizable menu.


The menu function

We need to declare a function to hold our menu logic, so we add another function into our header definition. Add a newline under the function int Program(), and write:

int Menu();


We also add the rought mockup in our implementation. Use the same process as when we created int Program() in tutorial number 2, and add the following code to GameManager.cpp:

int GameManager::Menu () {
  
} // END Menu


After that we need six controller variables; one to handle if the program is done looping and another one to store which menu option was selected to handle changing between menu screen. We also define a char (single character letter) to represent our cursor pointer. Finally we define the menus position on the x-axis and y-axis. Lets call them quit, select, choice, cursor, xmin and ymin.

To check if the loop is finished we need a boolean data type (either true or false), and to store which option was selected and which choice our menu-function returns we need two integers (numbers without decimals).

Maneuver back to your GameManager.h-file and under the access specifier private; add a variables called bool quit, one called int select and one called int choice.

Then we store the cursor in a variable called char cursor and set the cursors default starting position with int xmin and int ymin.

Your code should look like:

private:
  bool quit;
  int select;
  int choice;
  char cursor;
  int xmin;
  int ymin;


Implementing the menu

It's time to use these variables, so go back to the implementation (GameManager.cpp) and lets begin writing the menu loop.

C++ has a loop called a do-while loop. The benefit of this loop type is that it runs (atleast) once before exiting. A do-while loop is declared like this:

do {
  
} while (quit != true);


You could read this as do the code logic inside the loop body (the content inside the {} brackets) while end condition(s) isn't met.

To flesh this function out we use already existing libraries. We need to include these libraries into our code by adding them into our header-file.

Include <conio.h>, <vector> and <Windows.h> leaving your GameManager.h looking like:

#ifndef GAMEMANAGER_H
#define GAMEMANAGER_H
#include <iostream>
#include <conio.h>
#include <vector>
#include <Windows.h>
using namespace std;
class GameManager {   public:     GameManager();     int Program();     int Menu();   private:     bool quit;     int select;     int choice;     char cursor;     int xmin;     int ymin; }; // END GameManager
#endif


Lets keep coding to tie this all together. We begin by declaring a vector of strings (text). First we declare a storage for the menu options like this:

int GameManager::Menu () {
  vector<string> menuopt;
} // END Menu


Then we use a function called push_back(), found in the vector library, to add the options into the menuopt storage. To narrow the scope of this tutorial we only add the most nessecary options:

menuopt.push_back("New game");
menuopt.push_back("Quit");


Calling our Menu()-function should print the options on the screen, which is done with a for-loop:

for (int menuitem = 0; menuitem < menuopt.size(); menuitem++) {
  printf("  %s\n", menuopt[menuitem].c_str());
}


Read this out as for every menuitem stored in our vector of strings, as long as menuitem is less than the number of menuopts stored in the vector, print out a formatted textline and a newline, then increment the menuopt to the next position.

Note that we add two spaces to the beginning of every menu option to leave room for the cursor. Also note that you can get the size of the vector (number of elements) by calling the helper function size() located in the vector header, and that you need to use another helper function called c_str() to read an entire string to assign to the %s inside the printf-statement.

Our Menu() will now print out the options to screen, but how do we select an option?

Again we need a loop, and again a do-while is suitable.

We write a do-while loop and begin by adding our cursor to the screen.

do {
  printf("%c", cursor);
} while (!select && choice == 0);


The ! before the variable select is a relational operator and means not equal to. The double equal sign after the variable choice is an comparison operator and checks if the left value equals the right value. A single equal sign is called an assignment operator, and assigns a value to the variable.

If you compile this code you will notice that the cursor is positioned below the mockup text and the menu (and that the program loops uncontrollably). We need to tell our program to place the cursor at the correct spot on the screen.

The Windows.h library contains a predefined function to do just that, so lets utilize it.

The function is called SetConsoleCursorPostition() and it needs a handle to the console window and a X and a Y coordinate to know where to place terminal cursor. Go to GameManager.h and add the following under private:

COORD point;


You need to initialize your custom variables select and choice, define the cursor position and initialize the cursor.

Navigate back to GameManager.cpp and add the these lines under your vector of menu options:

select = false;
choice = 0;
xmin = 0;
ymin = 0;
point = {xmin, ymin};
cursor = '>';


Then add the code to position the cursor inside the do-while()-loop, just before printing the cursor:

SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), point);


Your updated for-loop should look like this:

do {
  SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), point);
  printf("%c", cursor);
} while (!select && choice == 0);


Now it's time to add the code to select which option we chose. This is done by using a switch-statement. Place the switch after positioning the cursor like this:

switch(select) {
  case 1: {
    choice = 1;
    break;
  }
  case 2: {
    choice = 2;
    break;
  }
  default: {
  }
}


Finally we need to be able to move the cursor to make our selection. Add a newline after the switch-statement and the following line:

char ch = _getch();


You need this codeline to flush the input buffer for excess keyboard input, to avoid having a value stored in the buffer when reading the next input (especially when using the arrow key, alt and ctrl).

But this doesn't let you chose a menu option. This is because we haven't coded a way to catch keyboard input. To do so we use Windows.h helper function GetAsyncKeyState().

Implementing the keyboard logic

We need to check if a certain key was pressed, and handle the logic assosiated with it. Lets begin by adding the logic for when the user presses up.

Add the following code after the input buffer flushing:

if (GetAsyncKeyState(VK_UP) != 0 && 0x8000) {
}


Here we check if the up-arrow is pressed (not equal to 0) and if a key is pressed (0x8000). The last check is just a security measurement to ensure a key actually is pressed.

We should check if the cursor is set inside our borders (first and last menu element) before handling any repositioning, so we add the following code inside the if-statement:

if(point.Y > ymin) {
  SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), point);
  printf(" ");
  point.Y = point.Y - 1;
  }
  ch = _getch();


We check if the cursor is within border margin, and if it is, print a space where the cursor used to be then resposition the cursor to be drawn at the new location next time the loop reaches printing the cursor to screen. We also clear the input buffer again.

Now to implement a way to handle down arrow being pressed. This time we write:

else if (GetAsyncKeyState(VK_DOWN) != 0 && 0x8000) {
  if (point.Y < menuopt.size() + (ymin - 1)) {
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), point);
    printf(" ");
    point.Y = point.Y + 1;
  }
  ch = _getch();
}


Again we check if the current key pressed is arrow down, that a key is acctually pressed and that the cursor stays within our borders. If it is we print out a blank space to clear the previous drawn position and reposition the cursor the be redrawn next loop iteration.

Implementing the possibility to select an option using the enter key could look like this:

else if (GetAsyncKeyState(VK_RETURN) != 0 && 0x8000) {
  if (point.Y == ymin) { choice = 1; };
  if (point.Y == ymin + 1) { choice = 2; };
}


Implementing a way to exit the program by pressing escape can be written:

if (GetAsyncKeyState(VK_ESCAPE) != 0 && 0x8000) {
  exit(0);
}


One last thing, to avoid the menu looping at full speed all the time we add a Sleep()-interval between iterations after the last if-statement.

Sleep(16);


We choose 16ms sleep interval because (one second) 1000 / 60 (60 frames per second) is 16.67ms per iteration.

Finally we return the choice we made after the ending bracket from the do-while()-statement:

return choice;


The finishing touch

It's time to test our Menu()-function. Delete the existing output statement inside your function Program() and in your main-function:

cout << "This output is from our new function Program()";
printf("Game programming tutorial");


Instead add the function call to your new Menu()-function in Program(). We implement this with the following code:

select = Menu();


When we have made our selection in the menu, the Menu()-function returns our selection (a number). This returned number is used to call other functions based on our selection. We add this into our code with this (Warning: rough mockup code ahead!):

switch (select) {
  case 1: {
    printf("New game was selected!");
    break;
  }
  case 2: {
    printf("Quit was selected!");
    break;
  }
}


Now recompile your program and test the output by making a selection in our new menu.

There you have it! Now you know how to create a simple menu function and switching between menu states using your keyboard. Next time I'll demonstrate how to separate your cursor positioning into an external function and how to add color to your program.


To see more - upvote this post and follow me @jonrhythmic

H2
H3
H4
3 columns
2 columns
1 column
14 Comments